Что это
Обычное расширение PlaceholderAPI. В сборках гуляет уже заражённая копия.Расширения лежат в
plugins/PlaceholderAPI/expansions/. Своего plugin.yml у них нет, в списке плагинов их нет. Папку читает сам PlaceholderAPI: на старте сервера и по /papi reload.Я открыл
Expansion-buildbattlepro.jar. Это расширение мини-игры BuildBattlePro, автор Drawethree, идентификатор buildbattlepro, версия 1.0.3. Плейсхолдеры на месте: статусы арен и статистика игрока. Эту часть писал автор расширения.Заражение рядом. В тот же пакет доложены классы
BuildBattleHook, а в register() первой строкой вставлен их запуск. В чистом расширении этой строки и этих классов нет.Дописанный хук делает следующее:
- на старте отправляет данные сервера на hurtlay.ovh
- примерно раз в десять секунд забирает оттуда команду
- одна команда выполняется в системе, вторая от имени консоли
- ответ консоли в
logs/latest.logне пишется
Мини-игра нужна расширению, чтобы зарегистрировать плейсхолдеры. Хуку она не нужна: его строка стоит выше проверки.
1) Что вставили в register()
Класс расширенияBuildBattleExpansion открыт, его не прятали. В заражённой копии canRegister() возвращает true, и PlaceholderAPI вызывает register() даже если BuildBattlePro на сервере нет.Чужая строка здесь первая. Дальше идёт своя логика расширения: взять плагин, и если его нет, плейсхолдеры не регистрировать.
Java:
public boolean canRegister() {
return true;
}
public boolean register() {
new BuildBattleHook((JavaPlugin) this.getPlaceholderAPI());
this.plugin = (BuildBattle) Bukkit.getPluginManager().getPlugin(this.getRequiredPlugin());
return this.plugin == null ? false : super.register();
}По байтам вставленный вызов занимает смещения 0–11. С 12-й инструкции расширение проверяет
BuildBattlePro. Плагина нет — метод возвращает false. Поток хука к этому моменту уже запущен, этот false его не останавливает.Хуку в конструктор передан сам PlaceholderAPI,
getPlaceholderAPI(). Задачи в планировщике Bukkit висят на нём.С мини-игрой расширение регистрируется и появляется в
/papi list. Без неё плейсхолдеров не будет. Хук от этого не зависит.2) Какие файлы доложили
Класс расширения один:
BuildBattleExpansion.class. В чистом jar на этом пакете он и заканчивается. В заражённой копии рядом девять файлов:BuildBattleHook.class— ядро. Соединение, опрос, хук на выключение сервераBuildBattleHook$1.class— пакет опросаBuildBattleHook$2.class— пакет на стартеBuildBattleHook$3.class— пакет на остановку JVMBuildBattleHook$4.class— вывод системной командыBuildBattleHook$5.class— вывод консольной командыBuildBattleHook$SpigotNotifyUpdate.class— поддельная консольBuildBattleHook$BuildBattleHook.class— вызовы спрятаны за invokedynamicBuildBattleHook$BuildBattleHook$BuildBattleHook.class— закрытые строки
3) Почему строкой это не находится
hurtlay.ovh поиском по jar не берётся. Строки закрыты в классах хука и открываются уже в памяти. Прямых ссылок на Runtime.exec, dispatchCommand и открытие URL в их импортах нет: вызовы идут через invokedynamic.Два служебных класса роняют декомпилятор. В атрибут Signature записана сломанная сигнатура, парсер падает до метода. Поля внутри хука названы непечатаемыми символами.
Класс расширения при этом читается целиком. В нём видна одна лишняя строка:
new BuildBattleHook(...).4) Как отличить чистую копию
Путь внутри jar: Код:
me/drawethree/buildbattleexpansion/У автора расширения в этой папке
BuildBattleExpansion.class. В заражённой копии рядом лежат BuildBattleHook.class и BuildBattleHook$....class. Самый заметный — BuildBattleHook$SpigotNotifyUpdate.class.Имя jar и автор Drawethree — это расширение. Заражение видно по этим лишним классам. Каждый такой jar со своим
BuildBattleHook работает сам.5) Что делать
- Остановить сервер.
- Открыть
plugins/PlaceholderAPI/expansions/. - Каждый jar открыть архиватором и посмотреть, нет ли внутри
BuildBattleHook. - Заражённую копию убрать из папки. Пока она лежит там, PlaceholderAPI поднимет её снова, в том числе через
/papi reload. - Плейсхолдеры нужны — скачать расширение у автора и перед запуском снова открыть архив.
BuildBattleHookвнутри быть не должно. - Соседние плагины эта вставка не переписывает. Менять из-за неё всю сборку не нужно.
- Запустить сервер. Маяк с удалённого jar прекращается. Если выход появился снова, заражённый файл вернулся из бэкапа или со старой сборкой.
6) Что хук делает с сервером
Расширение к этому моменту может уже вернутьfalse. BuildBattlePro на сервере может не стоять, в /papi list этой записи может не быть. Хуку это всё равно. Он запущен с первой строки register() и живёт до выключения JVM.Первым делом уходит маяк на hurtlay.ovh/handler.php. В нём версия ядра, IP, порт и имя ОС. На Linux к маяку добавляется ещё одно: можно ли писать в
/root/. На Windows маяк уходит тем же составом, без этой проверки. С той стороны сервер уже виден. В консоли Minecraft об этом нет ни слова.Дальше поднимается отдельный поток. Первый запрос уходит сразу, следующие примерно раз в десять секунд, пока сервер запущен. Поток не шлёт сообщение игрокам и не качает новый jar. Он спрашивает, есть ли команда.
Команда приезжает в ответе. Их две, и обе отрабатывают на этом же сервере.
Системная запускается на машине, где стоит сервер. Хук забирает и обычный вывод, и ошибку, склеивает их и отправляет обратно на тот же адрес. В консоль сервера и в
logs/latest.log этот запуск не пишется. Кто смотрит лог, тот этого процесса не видит. Кто сидит на том конце, получает текст, который команда вернула.Консольная выполняется как команда самого сервера. Для неё создаётся
SpigotNotifyUpdate. Это не консоль ядра. Это класс, который на неё подписался: isOp() всегда да, hasPermission() всегда да, имя отправителя Console. Команда уходит через dispatchCommand от плагина PlaceholderAPI, на главном потоке. Плагины, которые смотрят на отправителя, видят консоль с полными правами. Ограничение на игрока здесь ничего не решает: отправитель уже оператор.Текст, который такая команда написала бы в консоль, в лог не попадает. Его забирает внутренний буфер
SpigotNotifyUpdate и тем же запросом увозит обратно. С той стороны видно, что команда вернула. В latest.log пусто.Оба ответа уезжают отдельно: вывод системной команды своим пакетом, вывод консольной своим. Молчание сервера для администратора и полный ответ для той стороны.
На остановке JVM висит ещё один хук. Когда сервер гаснет, уходит последний маяк: сервер встал, снова IP, порт и версия. Включение и выключение с той стороны видны оба.
Адрес один, канал обычный HTTPS. Пока заражённый jar лежит в
expansions, команды пишет тот, кто отвечает по имени hurtlay.ovh. Расширение Drawethree тут только носитель. Работает дописанный BuildBattleHook.