꿈을꾸는 파랑새

반응형

오늘은 일본 암호화폐 거래소 비트뱅크(Bitbank) 사칭 악성코드인 Bitbank.apk에 대해 알아보겠습니다.
일단 비트뱅크(Bitbank)는 일본 최대의 암호화폐 거래소 중 하나이며 2014년 5월에 설립되었으며 대표이사(CEO)는 히로스에 노리유키(廣末紀之) 거래할 수 있는 암호화폐는 BTC, ETH, XRP, MONA, BCC, LTC 거래를 하고 있습니다.
해당 악성코드는 bitbankchains(.)com 를 통해서 유포되고 있으면 해당 악성코드 유포 사이트는 폐쇄되었습니다.

그리고 해당 Android(안드로이드) 및 iOS 앱을 다운로드하도록 작동을 하고 있습니다. 단 iOS 구성 프로필에서는 다운로드할 수 없습니다.
해쉬값은 다음과 같습니다.
파일명:Bitbank.apk
사이즈:3.91 MB
CRC32:46928de0
MD5:300ce5f75b7d5da0724d96af122a5d80
SHA-1:bc0324fab63da530bdab452741924b0584285b9d
SHA-256:f8a4ab3e0ae8216fa0fd455e6c1b861187463e761266c2a7aa0b68c062bb8cbe
DEX Base Info
Dex File Name:classes.dex
File Size:6115468 bytes
MD5:603a02581d642dbd2f3916d9052b582e
Class Size:5016
Method Size:45193
String Size:45648

악성코드 실행
악성코드 실행

2022.2.28 실행을 했을 때 502 에러가 나오는 것을 확인할 수가 있었습니다.
해당 악성코드 안드로이드 권한은 다음과 같습니다.

악성코드 안드로이드 권한
악성코드 안드로이드 권한

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE"/>
<uses-permission android:name="com.vivo.notification.permission.BADGE_ICON"/>

기본적으로 인터넷 연결, 와이파이 관련 권한 접근, 위치 찾기, 전화 걸기, 스마트폰 카메라 권한, 로그 읽기, 녹음, 진동, 설정 변경, 손전등 접근, 특정 네트워크 상태를 체크 해서 특정 네트워크를 키거나 끄는 기능 등이 추가된 것을 확인할 수가 있습니다.
p028io.dcloud.common.adapter.p030ui.webview.WebLoadEvent 부분에서 문자 관련 코드들이 있는것을 볼 수가 있고 여기서 보면 중국어가 보이는 것을 보아서 중국에서 일본에서 가상화폐를 하는 사람들을 타겟으로 만든 것이 아닐까 추측을 해 봅니다.
여기서 보이는 중국어인 检测拦截回调를 구글 번역기로 돌려보면 가로채기 콜백 감지라는 것을 확인할 수가 있었습니다.

문자 관련 코드
문자 관련 코드

@Override // android.webkit.WebViewClient
    public boolean shouldOverrideUrlLoading(WebView webView, String str) {
        if (this.mAdaWebview == null) {
            return false;
        }
        Logger.m1391e(TAG, "shouldOverrideUrlLoading url=" + str);
        AdaWebview adaWebview = this.mAdaWebview;
        adaWebview.mProgressIntValue = 0;
        adaWebview.mRecordLastUrl = str;
        if (adaWebview.checkOverrideUrl(str)) {
            Logger.m1391e(TAG, "检测拦截回调shouldOverrideUrlLoading url=" + str);
            AdaFrameView adaFrameView = this.mAdaWebview.mFrameView;
            adaFrameView.dispatchFrameViewEvents(AbsoluteConst.EVENTS_OVERRIDE_URL_LOADING, "{url:'" + str + "'}");
            return true;
        }
        if (this.mAdaWebview.mFrameView.getFrameType() == 5 || (this.mAdaWebview.mFrameView.getFrameType() == 2 && directPageIsLaunchPage(this.mAdaWebview.obtainApp()))) {
            this.mAdaWebview.obtainApp().updateDirectPage(str);
        }
        if (!shouldRuntimeHandle(str) && this.mAdaWebview.mFrameView.getFrameType() != 6) {
            try {
                if (str.startsWith("sms:")) {
                    int indexOf = str.indexOf("sms:");
                    int indexOf2 = str.indexOf("?");
                    if (indexOf2 == -1) {
                        this.mAdaWebview.getActivity().startActivity(new Intent("android.intent.action.VIEW", Uri.parse(str)));
                        return true;
                    }
                    String substring = str.substring(indexOf + 4, indexOf2);
                    String substring2 = str.substring(indexOf2 + 1);
                    Intent intent = new Intent("android.intent.action.VIEW", Uri.parse("sms:" + substring));
                    intent.putExtra("address", substring);
                    intent.putExtra("sms_body", substring2);
                    this.mAdaWebview.getActivity().startActivity(intent);
                } else if (str.startsWith("intent://")) {
                    Intent parseUri = Intent.parseUri(str, 1);
                    parseUri.addCategory("android.intent.category.BROWSABLE");
                    parseUri.setComponent(null);
                    if (Build.VERSION.SDK_INT >= 15) {
                        parseUri.setSelector(null);
                    }
                    if (this.mAdaWebview.getActivity().getPackageManager().queryIntentActivities(parseUri, 0).size() > 0) {
                        this.mAdaWebview.getActivity().startActivityIfNeeded(parseUri, -1);
                    }
                } else {
                    AdaWebview adaWebview2 = this.mAdaWebview;
                    if (!(adaWebview2 == null || adaWebview2.getActivity() == null || !this.mAdaWebview.obtainApp().checkSchemeWhite(str))) {
                        this.mAdaWebview.getActivity().startActivity(new Intent("android.intent.action.VIEW", Uri.parse(str)));
                    }
                }
            } catch (Exception unused) {
                Logger.m1391e(TAG, "ActivityNotFoundException url=" + str);
            }
            return true;
        } else if (!PdrUtil.isEmpty(this.mdcloudwebviewclientlister)) {
            return this.mdcloudwebviewclientlister.shouldOverrideUrlLoading(webView, str);
        } else {
            return false;
        }
    }

p028io.dcloud.common.util.TelephonyUtil 부분에서는 스마트폰의 IMEI 관련 코드들이 있는 것을 확인할 수가 있습니다.

IMEI 관련 코드
IMEI 관련 코드

private static java.lang.String[] getMultiIMEI(android.content.Context r11) {
        /*
            Method dump skipped, instructions count: 290
            To view this dump change 'Code comments level' option to 'DEBUG'
        */
        throw new UnsupportedOperationException("Method not decompiled: p028io.dcloud.common.util.TelephonyUtil.getMultiIMEI(android.content.Context):java.lang.String[]");
    }

    private static Object getPhoneInfo(int i, Context context) {
        Object systemService;
        String a;
        int i2;
        try {
            systemService = context.getSystemService("phone");
            a = C1309a.m186a("b218W31qe2t6YWptekFs");
            i2 = Build.VERSION.SDK_INT;
        } catch (Exception unused) {
        }
        if (i2 > 21) {
            return ReflectUtils.invokeMethod(systemService, a, new Class[]{Integer.TYPE}, new Object[]{Integer.valueOf(i)});
        }
        if (i2 == 21) {
            return ReflectUtils.invokeMethod(systemService, a, new Class[]{Long.TYPE}, new Object[]{Integer.valueOf(i)});
        }
        return null;
    }

    public static String getSBBS(Context context, boolean z, boolean z2) {
        return getSBBS(context, z, z2, true);
    }

    private static int getSubId(int i, Context context) {
        Uri parse = Uri.parse("content://telephony/siminfo");
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = null;
        try {
            cursor = contentResolver.query(parse, new String[]{"_id", "sim_id"}, "sim_id = ?", new String[]{String.valueOf(i)}, null);
        } catch (Exception unused) {
            if (cursor == null) {
                return -1;
            }
        } catch (Throwable th) {
            if (cursor != null) {
                cursor.close();
            }
            throw th;
        }
        if (cursor == null || !cursor.moveToFirst()) {
            if (cursor == null) {
                return -1;
            }
            cursor.close();
            return -1;
        }
        int i2 = cursor.getInt(cursor.getColumnIndex("_id"));
        if (cursor != null) {
            cursor.close();
        }
        return i2;
    }

    public static String getUUID(Context context) {
        String str = "";
        if (context == null) {
            return str;
        }
        if (!TextUtils.isEmpty(muuid)) {
            return muuid;
        }
        try {
            String string = Settings.System.getString(context.getContentResolver(), C1309a.m186a("aWZsemdhbFdhbA=="));
            if (string != null) {
                str = string;
            }
            return str;
        } finally {
            muuid = str;
        }
    }

    public static String getWifiData(Context context) {
        Object invokeMethod;
        Object systemService = context.getSystemService(C1309a.m186a("f2FuYQ=="));
        if (systemService == null || (invokeMethod = ReflectUtils.invokeMethod(systemService, C1309a.m186a("b218S2dmZm1rfGFnZkFmbmc"), new Class[0], new Object[0])) == null || invokeMethod == null) {
            return null;
        }
        Object invokeMethod2 = ReflectUtils.invokeMethod(invokeMethod, C1309a.m186a("b218RWlrSWxsem17ew"), new Class[0], new Object[0]);
        String str = invokeMethod2 != null ? (String) invokeMethod2 : null;
        if (!TextUtils.isEmpty(str)) {
            return str.replace(":", "");
        }
        return null;
    }

    private static boolean isUnValid(String str) {
        return TextUtils.isEmpty(str) || str.contains("Unknown") || str.contains("00000000");
    }

    private static String savePublicFile(File file, File file2, String str, String str2, Context context) throws IOException {
        String str3;
        if (!file.exists() || file.length() <= 0) {
            return createRandomBSFile(context, file, file2, UUID_FILE_NAME);
        }
        try {
            str3 = IOUtil.toString(new FileInputStream(file));
            if (file2 == null) {
                return str3;
            }
            try {
                if (FileUtil.needMediaStoreOpenFile(context)) {
                    return str3;
                }
                if (!file2.getParentFile().exists()) {
                    file2.getParentFile().mkdirs();
                    file2.createNewFile();
                }
                DHFile.copyFile(str, str2);
                return str3;
            } catch (Exception unused) {
                return str3 == null ? createRandomBSFile(context, file, file2, UUID_FILE_NAME) : str3;
            }
        } catch (Exception unused2) {
            str3 = null;
        }
    }

    public static String updateIMEI(Context context) {
        if (!PdrUtil.isEmpty(mImei)) {
            return mImei;
        }
        String[] multiIMEI = getMultiIMEI(context);
        if (multiIMEI != null) {
            StringBuilder sb = new StringBuilder();
            for (String str : multiIMEI) {
                sb.append(str);
                sb.append(JSUtil.COMMA);
            }
            if (sb.lastIndexOf(JSUtil.COMMA) >= sb.length() - 1) {
                String sb2 = sb.deleteCharAt(sb.length() - 1).toString();
                mImei = sb2;
                return sb2;
            }
            String sb3 = sb.toString();
            mImei = sb3;
            return sb3;
        }
        mImei = "";
        return "";
    }

    public static String getIMEI(Context context, boolean z) {
        return getIMEI(context, z, false);
    }

그리고 유심(USIM)관련 코드들도 볼 수가 있습니다.

유심 관련 코드
유심 관련 코드

private static int getSubId(int i, Context context) {
        Uri parse = Uri.parse("content://telephony/siminfo");
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = null;
        try {
            cursor = contentResolver.query(parse, new String[]{"_id", "sim_id"}, "sim_id = ?", new String[]{String.valueOf(i)}, null);
        } catch (Exception unused) {
            if (cursor == null) {
                return -1;
            }
        } catch (Throwable th) {
            if (cursor != null) {
                cursor.close();
            }
            throw th;
        }
        if (cursor == null || !cursor.moveToFirst()) {
            if (cursor == null) {
                return -1;
            }
            cursor.close();
            return -1;
        }
        int i2 = cursor.getInt(cursor.getColumnIndex("_id"));
        if (cursor != null) {
            cursor.close();
        }
        return i2;
    }

    public static String getUUID(Context context) {
        String str = "";
        if (context == null) {
            return str;
        }
        if (!TextUtils.isEmpty(muuid)) {
            return muuid;
        }
        try {
            String string = Settings.System.getString(context.getContentResolver(), C1309a.m186a("aWZsemdhbFdhbA=="));
            if (string != null) {
                str = string;
            }
            return str;
        } finally {
            muuid = str;
        }
    }

    public static String getWifiData(Context context) {
        Object invokeMethod;
        Object systemService = context.getSystemService(C1309a.m186a("f2FuYQ=="));
        if (systemService == null || (invokeMethod = ReflectUtils.invokeMethod(systemService, C1309a.m186a("b218S2dmZm1rfGFnZkFmbmc"), new Class[0], new Object[0])) == null || invokeMethod == null) {
            return null;
        }
        Object invokeMethod2 = ReflectUtils.invokeMethod(invokeMethod, C1309a.m186a("b218RWlrSWxsem17ew"), new Class[0], new Object[0]);
        String str = invokeMethod2 != null ? (String) invokeMethod2 : null;
        if (!TextUtils.isEmpty(str)) {
            return str.replace(":", "");
        }
        return null;
    }

    private static boolean isUnValid(String str) {
        return TextUtils.isEmpty(str) || str.contains("Unknown") || str.contains("00000000");
    }

그리고 해당 악성코드에 포함된 인터넷 주소는 다음과 같습니다.

https(:)//localhost
https://96f0e031-f37a-48ef-84c7-2023f6360c0a.bspapp(.)com/http/splash-screen/report
http://ask.dcloud(.)net.cn/article/35058
https://stream.dcloud(.)net(.)cn/
https://service.dcloud(.)net(.)cn/sta/so?p=a&pn=%s&ver=%s&appid=%s
https://stream.mobihtml5(.)com/
https://96f0e031-f37a-48ef-84c7-2023f6360c0a.bspapp(.)com/http/rewarded-video/report?p=a&t=
http(:)//localhost
http://schemas.android(.)com/apk/res/android
http://m3w(.)cn/s/
http://ask.dcloud.net(.)cn/article/282
http://ask.dcloud.net(.)cn/article/283
https://ask.dcloud.net(.)cn/article/35877
https://ask.dcloud.net(.)cn/article/35627
http://ask.dcloud.net(.)cn/article/287

악성코드 인증서 내용은 다음과 같습니다.

악성코드 인증서 정보
악성코드 인증서 정보

BITBANK.RSA (META-INF/BITBANK.SF)
유형: X.509
버전: 3
시리얼 번호: 0x5947f5f8
소유자: CN=yourcomname, OU=IT, O=yourcomname, C=CN
유효 시작 시각: Fri Oct 29 16:33:13 GMT+09:00 2021
유효 종료 시각: Sun Oct 05 16:33:13 GMT+09:00 2121
공개키 타입: RSA
지수: 65537
모듈러스 크기 (비트): 2048
모듈러스: 17232284414961495723018745263162065508414951938371099140103728726612818742636596122791501044994857341755217962335857110365395459648727548268138570107922305438500891304803421208962694651684831675330930427182941942293183070413904318616865166598176449099815682646394136618538665408260637679687024414557921515294639125257822116199727197133098614058005753991889950154181220509057787316213688909185307203135022755571204772800681832664787173656989414008129216805634453803185118426741437303576650967552382907201694711082095773625550721224458083679560167421634966319317676139241307222672817351569793101633571780273806990041531
서명 유형: SHA256withRSA
서명 OID: 1.2.840.113549.1.1.11
MD5 지문: B9 27 25 AC B7 11 FB D6 4E 78 8A 65 80 B8 2E EE 
SHA-1 지문: BC EF 12 58 50 EF 07 42 F5 7D 33 CB 05 C9 E4 24 1A 56 40 8B 
SHA-256 지문: 57 E3 07 D6 39 36 D6 79 FC CD FB AA 00 51 E1 C6 62 F4 C6 DF D0 ED 32 FE A6 EE 01 DB 15 B5 85 BB

2022-02-28 09:52:03 UTC 기준으로 바이러스토탈에서 탐지하는 보안 제품들은 다음과 같습니다.
DrWeb:Android.FakeApp.903
ESET-NOD32:Android/Spy.Banker.BJY
Fortinet:Adware/Agent!Android
Ikarus:Trojan.AndroidOS.Banker
K7GW:Trojan ( 0058b6fa1 )
Kaspersky:HEUR:Trojan.AndroidOS.Fakeapp.a
Lionic:Trojan.AndroidOS.Fakeapp.C!c
Symantec Mobile Insight:AppRisk:Generisk
Trustlook:Android.PUA.General
기본적으로 백신앱들을 설치를 해서 실시간 감시, 실시간 업데이트를 해두고 문자 등으로 오는 이메일과 안드로이드 스마트폰 기준으로 외부에서 앱을 설치하는 것을 말아야 할 것입니다.

반응형
그리드형

댓글

비밀글모드

  1. 악성 코드는 정말 끊임없이 나오는군요
    2022.03.04 06:29 신고
    • 네~계속 나옵니다.
      2022.03.04 17:10 신고
  2. 악성코드는 그야말로 암과 같은 존재같아요
    잘 보고 갑니다. ^^
    2022.03.04 08:32 신고
    • 기본적인 보안수칙을 지키면 어느정도 피해를 줄일수가 있습니다.
      2022.03.04 17:10 신고
  3. 암호화폐는 참 여러모로 이슈가 많네요.
    뭐 이런 악성코드는 암호화폐에 국한되는 건 아니지만요.
    2022.03.04 17:01 신고
    • 돈이 되는것을 많이 노리다보니 그런것 같습니다.
      2022.03.04 17:11 신고
  4. 정말 조심해야 할 것 같아요~~ 제대로 잡을 수 없을까 늘 생각하게 되네요
    2022.03.04 17:35 신고
    • 기본적인 보안 수칙을 지키는것이 중요 합니다.
      2022.03.04 20:30 신고