Приветствую всех читателей этой статьи и посетителей <Codeby.net> 🖐
Хочу рассказать о шелл-кодах и особенностях их написания вручную. Вам понадобятся знания ассемблера на базовом уровне. Рассмотрим как пишут шелл-коды без инструментов, которые могут их автоматически создать. Вредоносные шелл-коды писать не будем! Будем писать псевдо шелл-коды для простоты и понимания. Если эта статья и её формат вам понравиться, тогда расскажу о вредоносных шелл-кодах
Написание шелл-кода будет показано для архитектуры x86. Алгоритм не сильно отличается для архитектуры x64. Для практики я рекомендую вам установить Linux в VirtualBox или VMware. Так же можно экспортировать готовый образ виртуальной машины.
План:
Теория: Что такое шелл-код и системные вызовы
Практика: Сравниваем программу на ассемблере и языке Си. Делаем hello world в виде шелл-кода
Что такое шелл-код и системные вызовы
Шелл-код — это двоичный исполняемый код, который выполняет определенную задачу. Например: Передать управление
Программы взаимодействуют с операционной системой через функции. Функции расположены в библиотеках. Функция
Системные вызовы не зависят от версии какой-либо из библиотеки. Из-за универсальности системные вызовы используют в шелл-кодах.
У системных вызовов есть кода. Например, функция
Машины с архитектурой x86: Системные вызовы определены в файле
Машины с архитектурой x64: Системные вызовы определены в файле
Проверим существование системных вызовов на практике
Напишем программу на языке Си, печатающую строку BUG.
Код:
Компиляция:
Проверим наличие системных вызовов с помощью команды:
Вывод strace
В конце strace мы можем видеть системный вызов
Сравниваем программу на ассемблере и языке Си
Шелл-код можно написать, как программу на языке Си, скомпилировать, при необходимости отредактировать и перевести в байтовое представление. Такой способ подходит, если мы пишем сложный шелл-код.
Шелл-код можно написать на языке ассемблер. Этот способ я хочу рассмотреть более подробно. Для сравнения мы напишем 2 программы, печатающие сроку Hello world!. Первая будет написана на языке Си, а вторая на ассемблере.
Код на языке Си:
Компиляция:
Код на ассемблере:
Получаем объектный файл с помощью nasm:
Объединяем объектный файл в один исполняемый:
Посмотрим на ассемблерный код получившихся файлов с помощью
Функция main в программе на языке Си:
Ассемблер:
Кажется, что больше кода в ассемблерном листинге, но это не так. В листинге языка Си я показал только функцию main, а она там не одна! В листинге ассемблера я показал программу целиком!
Делаем hello world в виде шелл-кода
Взгляните на листинг программы, написанной на ассемблере. Сначала идут адреса, затем байты, а далее инструкции (
Вручную всё делать очень не удобно. Bash нам в помощь:
Опкоды ( представлены в читаемом виде )
Но работать этот шелл-код не будет, так как в нём присутствуют байты
Убираем точные адреса
Строка, которую нам нужно напечатать - Hello, world! Представим её в виде байтов. Утилита
Байтовое представление строки Hello, world!:
Теперь превратим их в инструкции на ассемблере. Тут нам поможет фреймворк
Получаем опкоды инструкций
Флаг -a x86 -b 32 обозначают вывод для архитектуры x86.
Чтобы передать байты в стек нужна инструкция
Как будет выглядить код на ассемблере
В итоге получаем:
Замена нулевых байтов
Для удобства мы представим эти инструкции в виде ассемблерных команд. Нам поможет утилита
Вывод утилиты ndisasm
Инструкции, содержащие нулевые байты
Нам нужно заменить инструкции с нулевыми байтами на другие. Нулевые байты образуются из-за того, что инструкция
Ассемблерные инструкции и их опкоды
Итоговый вариант Hello, World! в виде шелл-кода
Оформим весь этот набор байтов в виде программы на языке Си.
Код программы
Компилируем:
Проверяем работоспособность:
Довольно долго это всё делать, если вы не хотите делать шелл-код для атаки на определённую компанию.
Существует замечательный инструменты Msfvenom и подобные ему. Msfvenom позволяет делать шелл-код по шаблону и даже закодировать его. Про этот инструмент и про сам metasploit на Codeby.net написано много информации. Про энкодеры информации в интернете тоже достаточно. Например:
Хочу порекомендовать сайты:
Желаю вам удачи и здоровья. Не болейте и прокачивайте мозги.
Хочу рассказать о шелл-кодах и особенностях их написания вручную. Вам понадобятся знания ассемблера на базовом уровне. Рассмотрим как пишут шелл-коды без инструментов, которые могут их автоматически создать. Вредоносные шелл-коды писать не будем! Будем писать псевдо шелл-коды для простоты и понимания. Если эта статья и её формат вам понравиться, тогда расскажу о вредоносных шелл-кодах
Написание шелл-кода будет показано для архитектуры x86. Алгоритм не сильно отличается для архитектуры x64. Для практики я рекомендую вам установить Linux в VirtualBox или VMware. Так же можно экспортировать готовый образ виртуальной машины.
План:
Теория: Что такое шелл-код и системные вызовы
Практика: Сравниваем программу на ассемблере и языке Си. Делаем hello world в виде шелл-кода
Что такое шелл-код и системные вызовы
Шелл-код — это двоичный исполняемый код, который выполняет определенную задачу. Например: Передать управление
Ссылка скрыта от гостей
(/bin/sh
) или даже выключить компьютер. Шелл-код пишут на языке ассемблер с помощью опкодов (Например: \x90
означает команду:nop
).Программы взаимодействуют с операционной системой через функции. Функции расположены в библиотеках. Функция
printf()
, exit()
в библиотеке libc
. Помимо функций существуют системные вызовы. Системные вызовы находятся в ядре операционной системы. Взаимодействие с операционной системой происходит через системные вызовы. Функции используют системные вызовы.Системные вызовы не зависят от версии какой-либо из библиотеки. Из-за универсальности системные вызовы используют в шелл-кодах.
У системных вызовов есть кода. Например, функция
printf()
использует системный вызов write()
с кодом 4.Машины с архитектурой x86: Системные вызовы определены в файле
/usr/include/i386-linux-gnu/asm/unistd_32.h
Машины с архитектурой x64: Системные вызовы определены в файле
/usr/include/x86_64-linux-gnu/asm/unistd_64.h
Ссылка скрыта от гостей
с объяснениями.Проверим существование системных вызовов на практике
Напишем программу на языке Си, печатающую строку BUG.
Код:
C:
#include <stdio.h>
void main(void) { printf("BUG"); }
Компиляция:
gcc printf_prog.c -o printf_prog
Проверим наличие системных вызовов с помощью команды:
strace ./printf_prog
Вывод strace
C:
execve("./printf_prog", ["./printf_prog"], 0xbffff330 /* 48 vars */) = 0
brk(NULL) = 0x405000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fcf000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=92992, ...}) = 0
mmap2(NULL, 92992, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fb8000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\254\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1947056, ...}) = 0
mmap2(NULL, 1955712, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7dda000
mprotect(0xb7df3000, 1830912, PROT_NONE) = 0
mmap2(0xb7df3000, 1368064, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19000) = 0xb7df3000
mmap2(0xb7f41000, 458752, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x167000) = 0xb7f41000
mmap2(0xb7fb2000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d7000) = 0xb7fb2000
mmap2(0xb7fb5000, 10112, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7fb5000
close(3) = 0
set_thread_area({entry_number=-1, base_addr=0xb7fd00c0, limit=0x0fffff, seg_32bit=1, contents=0, read_exec_only=0, limit_in_pages=1, seg_not_present=0, useable=1}) = 0 (entry_number=6)
mprotect(0xb7fb2000, 8192, PROT_READ) = 0
mprotect(0x403000, 4096, PROT_READ) = 0
mprotect(0xb7ffe000, 4096, PROT_READ) = 0
munmap(0xb7fb8000, 92992) = 0
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0), ...}) = 0
brk(NULL) = 0x405000
brk(0x426000) = 0x426000
brk(0x427000) = 0x427000
write(1, "BUG", 3BUG) = 3
exit_group(3) = ?
+++ exited with 3 +++
В конце strace мы можем видеть системный вызов
write(1, "BUG", 3BUG)
. Количество кода для шелл-кода слишком много, если использовать функции. Старайтесь писать небольшие шелл-коды. Так они будут меньше обнаруживаться и вероятность их срабатывания будет больше.Сравниваем программу на ассемблере и языке Си
Шелл-код можно написать, как программу на языке Си, скомпилировать, при необходимости отредактировать и перевести в байтовое представление. Такой способ подходит, если мы пишем сложный шелл-код.
Шелл-код можно написать на языке ассемблер. Этот способ я хочу рассмотреть более подробно. Для сравнения мы напишем 2 программы, печатающие сроку Hello world!. Первая будет написана на языке Си, а вторая на ассемблере.
Код на языке Си:
C:
#include <stdio.h>
void main(void) { printf("Hello, world!"); }
Компиляция:
gcc hello_world_c.c -o hello_world_c
Код на ассемблере:
C-подобный:
global _start
section .text
_start:
mov eax, 4 ; номер системного вызова (sys_write)
mov ebx, 1 ; файловый дескриптор (stdout)
mov ecx, hello_world ; сообщение hello_world
mov edx, len_hello ; длина строки hello_world
int 0x80 ; вызов системного прерывания
mov eax, 1 ; номер системного вызова (sys_exit)
xor ebx, ebx ; Обнуляем регистр ebx, чтобы первый аргумент системного вызова sys_exit был равен 0
int 0x80 ; вызов системного прерывания
hello_world: db "Hello, world!", 10 ; 10 - количество выделенных байт для строки
len_hello: equ $ - hello_world ; вычиляем длину строки. $ указывает на строку hello_world
Получаем объектный файл с помощью nasm:
nasm -f elf32 hello_world.asm -o hello_world.o
Объединяем объектный файл в один исполняемый:
ld -m elf_i386 hello_world.o -o hello_world
В ассемблерном коде присутствует инструкция int 0x80. Это системное прерывание. Когда процессор получает прерывание
0x80
, он выполняет запрашиваемый системный вызов в режиме ядра, при этом получая нужный обработчик из Interrupt Descriptor Table (таблицы описателей прерываний). Номер системного вызова задаётся в регистре EAX. Аргументы функции должны содержаться в регистрах EBX, ECX, EDX, ESI, EDI и EBP. Если функция требует более шести аргументов, то необходимо поместить их в структуру и сохранить указатель на первый элемент этой структуры в регистр EBX.Посмотрим на ассемблерный код получившихся файлов с помощью
objdump
.Функция main в программе на языке Си:
C-подобный:
1199: 8d 4c 24 04 lea ecx,[esp+0x4]
119d: 83 e4 f0 and esp,0xfffffff0
11a0: ff 71 fc push DWORD PTR [ecx-0x4]
11a3: 55 push ebp
11a4: 89 e5 mov ebp,esp
11a6: 53 push ebx
11a7: 51 push ecx
11a8: e8 24 00 00 00 call 11d1 <__x86.get_pc_thunk.ax>
11ad: 05 53 2e 00 00 add eax,0x2e53
11b2: 83 ec 0c sub esp,0xc
11b5: 8d 90 08 e0 ff ff lea edx,[eax-0x1ff8]
11bb: 52 push edx
11bc: 89 c3 mov ebx,eax
11be: e8 6d fe ff ff call 1030 <printf@plt>
11c3: 83 c4 10 add esp,0x10
11c6: 90 nop
11c7: 8d 65 f8 lea esp,[ebp-0x8]
11ca: 59 pop ecx
11cb: 5b pop ebx
11cc: 5d pop ebp
11cd: 8d 61 fc lea esp,[ecx-0x4]
11d0: c3 ret
Ассемблер:
C-подобный:
08049000 <_start>:
8049000: b8 04 00 00 00 mov eax,0x4
8049005: bb 01 00 00 00 mov ebx,0x1
804900a: b9 1f 90 04 08 mov ecx,0x804901f
804900f: ba 0e 00 00 00 mov edx,0xe
8049014: cd 80 int 0x80
8049016: b8 01 00 00 00 mov eax,0x1
804901b: 31 db xor ebx,ebx
804901d: cd 80 int 0x80
0804901f <hello_world>:
804901f: 48 dec eax
8049020: 65 6c gs ins BYTE PTR es:[edi],dx
8049022: 6c ins BYTE PTR es:[edi],dx
8049023: 6f outs dx,DWORD PTR ds:[esi]
8049024: 2c 20 sub al,0x20
8049026: 77 6f ja 8049097 <hello_world+0x78>
8049028: 72 6c jb 8049096 <hello_world+0x77>
804902a: 64 21 0a and DWORD PTR fs:[edx],ecx
Кажется, что больше кода в ассемблерном листинге, но это не так. В листинге языка Си я показал только функцию main, а она там не одна! В листинге ассемблера я показал программу целиком!
Делаем hello world в виде шелл-кода
Взгляните на листинг программы, написанной на ассемблере. Сначала идут адреса, затем байты, а далее инструкции (
8049000: b8 04 00 00 00 mov eax, 0x4
). Запишем опкоды инструкций в виде шелл-кода.Вручную всё делать очень не удобно. Bash нам в помощь:
objdump -d ./hello_world|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
(вместо ./hello_world
можно подставить любую другую программу ).Опкоды ( представлены в читаемом виде )
C:
"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x1f\x90\x04\x08"
"\xba\x0e\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\x31\xdb\xcd"
"\x80\x48\x65\x6c\x6c"
"\x6f\x2c\x20\x77\x6f"
"\x72\x6c\x64\x21\x0a"
Но работать этот шелл-код не будет, так как в нём присутствуют байты
\x00
и строка hello_world указана по адресу ( "\xb9\x1f"\x90\x04\x08"
- это инструкция mov ecx, 0x8040901f
), а в программе адрес может быть разный из-за механизма защиты
Ссылка скрыта от гостей
. В шелл-коде точных адресов быть не должно. Решим проблему постепенно, начав заменять данные, расположенные по точному адресу, а затем уберём байты \x00
.Убираем точные адреса
Строка, которую нам нужно напечатать - Hello, world! Представим её в виде байтов. Утилита
xxd
нам поможет: echo "Hello, World!" | xxd -pu
Байтовое представление строки Hello, world!:
48656c6c6f2c20576f726c64210a
. Для удобства разделим по 4 всю последовательность байтов: 48656c6c 6f2c2057 6f726c64 210a
. Байтов в конце недостаточно. Во всех отделённых нами наборов байтов, их по 4, а в последнем всего лишь 2. Добавим любые байты кроме \x00
, так как потом добавленные нами байты обрежутся программой. Я выберу байты \x90
. Нам нужно расположить байты в порядке: little-enidan ( в обратном порядке ). Получится такая последовательность байт: 90900a21 646c726f 57202c6f 6c6c6548
. Это просто байты строки.Теперь превратим их в инструкции на ассемблере. Тут нам поможет фреймворк
Ссылка скрыта от гостей
с утилитой rasm2.Получаем опкоды инструкций
Bash:
rasm2 -a x86 -b 32 "push 0x90900a21"
rasm2 -a x86 -b 32 "push 0x646c726f"
rasm2 -a x86 -b 32 "push 0x57202c6f"
rasm2 -a x86 -b 32 "push 0x6c6c6548"
rasm2 -a x86 -b 32 "mov ecx, esp"
Чтобы передать байты в стек нужна инструкция
push
. Регистр [/COLOR]esp[COLOR=rgb(97, 189, 109)]
указывает на вершину стека. Переместим на значение вершине стека в регистр ecx
.Команда PUSH размещает значение в стеке, т.е. помещает значение в ячейку памяти, на которую указывает регистр ESP, после этого значение регистра ESP увеличивается на 4.
Как будет выглядить код на ассемблере
C-подобный:
push 90900a21
push 646c726f
push 57202c6f
push 6c6c6548
mov ecx, esp
В итоге получаем:
68210a9090 686f726c64 686f2c2057 6848656c6c 89e1
. Заменим точный адрес в нашем шелл-коде на новые инструкции.
C:
"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\x68\x21\x0a\x90\x90"
"\x68\x6f\x72\x6c\x64"
"\x68\x6f\x2c\x20\x57"
"\x68\x48\x65\x6c\x6c"
"\x89\xe1"
"\xba\x0e\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\x31\xdb\xcd"
"\x80\x48\x65\x6c\x6c"
"\x6f\x2c\x20\x77\x6f"
"\x72\x6c\x64\x21\x0a"
Замена нулевых байтов
Для удобства мы представим эти инструкции в виде ассемблерных команд. Нам поможет утилита
ndisasm
. Первым делом запишем наши байты в файл, а затем применим утилиту ndisasm
.
Bash:
echo -ne '\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\x68\x21\x0a\x90\x90\x68\x6f\x72\x6c\x64\x68\x6f\x2c\x20\x57\x68\x48\x65\x6c\x6c\x89\xe1\xba\x0e\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\x31\xdb\xcd\x80\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21\x0a' > test
ndisasm -b32 test
Вывод утилиты ndisasm
C-подобный:
00000000 B804000000 mov eax,0x4
00000005 BB01000000 mov ebx,0x1
0000000A 68210A9090 push dword 0x90900a21
0000000F 686F726C64 push dword 0x646c726f
00000014 686F2C2057 push dword 0x57202c6f
00000019 6848656C6C push dword 0x6c6c6548
0000001E 89E1 mov ecx,esp
00000020 BA0E000000 mov edx,0xe
00000025 CD80 int 0x80
00000027 B801000000 mov eax,0x1
0000002C 31DB xor ebx,ebx
0000002E CD80 int 0x80
00000030 48 dec eax
00000031 656C gs insb
00000033 6C insb
00000034 6F outsd
00000035 2C20 sub al,0x20
00000037 776F ja 0xa8
00000039 726C jc 0xa7
0000003B 64210A and [fs:edx],ecx
Инструкции, содержащие нулевые байты
C-подобный:
00000000 B804000000 mov eax,0x4
00000005 BB01000000 mov ebx,0x1
00000020 BA0E000000 mov edx,0xe
00000027 B801000000 mov eax,0x1
Нам нужно заменить инструкции с нулевыми байтами на другие. Нулевые байты образуются из-за того, что инструкция
mov
- двухбайтовая, а оставшиеся 2 байта из 4 компилятору нужно заменить нулями. Предлагаю заменить эти инструкции mov на сочетание двухбайтовых инструкций xor
и mov
.Ассемблерные инструкции и их опкоды
C-подобный:
xor eax, eax ; \x31\xc0
mov al, 4 ; \xb0\x04
xor ebx, ebx ; \x31\xdb
mov bl, 1 ; \xb3\x01
xor edx, edx ; \x31\xd2
mov dl, 14 ; \xb2\x0e
xor eax, eax ; \x31\xc0
mov al, 1 ; \xb0\x01
Итоговый вариант Hello, World! в виде шелл-кода
C-подобный:
"\x31\xc0\xb0\x04"
"\x31\xdb\xb3\x01"
"\x68\x21\x0a\x90\x90"
"\x68\x6f\x72\x6c\x64"
"\x68\x6f\x2c\x20\x57"
"\x68\x48\x65\x6c\x6c"
"\x89\xe1"
"\x31\xd2\xb2\x0e"
"\xcd\x80"
"\x31\xc0\xb0\x01"
"\x31\xdb\xcd"
"\x80\x48\x65\x6c\x6c"
"\x6f\x2c\x20\x77\x6f"
"\x72\x6c\x64\x21\x0a"
Оформим весь этот набор байтов в виде программы на языке Си.
Код программы
C:
unsigned char hello_world[]=
// Заменённые инструкции
//"\xb8\x04\x00\x00\x00" mov eax,0x4
"\x31\xc0\xb0\x04"
//"\xbb\x01\x00\x00\x00" mov ebx,0x1
"\x31\xdb\xb3\x01"
"\x68\x21\x0a\x90\x90"
"\x68\x6f\x72\x6c\x64"
"\x68\x6f\x2c\x20\x57"
"\x68\x48\x65\x6c\x6c"
"\x89\xe1"
//"\xba\x0e\x00\x00\x00" mov edx,0xe
"\x31\xd2\xb2\x0e"
"\xcd\x80"
//"\xba\x01\x00\x00\x00" mov eax,0x1
"\x31\xc0\xb0\x01"
"\x31\xdb\xcd"
"\x80\x48\x65\x6c\x6c"
"\x6f\x2c\x20\x77\x6f"
"\x72\x6c\x64\x21\x0a";
void main() {
int (*ret)() = (int(*)())hello_world;
ret();
}
Ссылка скрыта от гостей
Компилируем:
gcc hello_world_test.c -o hello_world_test -z execstack
Проверяем работоспособность:
./hello_world_test
Довольно долго это всё делать, если вы не хотите делать шелл-код для атаки на определённую компанию.
Существует замечательный инструменты Msfvenom и подобные ему. Msfvenom позволяет делать шелл-код по шаблону и даже закодировать его. Про этот инструмент и про сам metasploit на Codeby.net написано много информации. Про энкодеры информации в интернете тоже достаточно. Например:
Ссылка скрыта от гостей
.Хочу порекомендовать сайты:
Ссылка скрыта от гостей
и
Ссылка скрыта от гостей
. На этих сайтах вы сможете найти множество шелл-кодов.Желаю вам удачи и здоровья. Не болейте и прокачивайте мозги.
Последнее редактирование: