꿈을꾸는 파랑새

오늘은 구글 플레이 스토어 에서 2021년부터 구글 플레이 스토어에서 유통이 되어서 10k 이상 다운로드가 되어서 화면이 꺼져 있는 동안 광고를 로드 하고 보이지 않는 광고에 비용을 지급하는 광고주뿐만 아니라 사용자에게도 영향을 주며 안드로이드 스마트폰에서 배터리 소모, 데이터 소모, Clicker 행동으로 말미암은 정보 유출 및 사용자 프로파일링 중단과 같은 잠재적 위험이 됩니다.

대표적으로 V/DMB 플레이어,트로트 음악 다운로더,뉴스 및 캘린더 같은 걸로 위장을 하고 있습니다.
일단 유포된 주소는 다음과 같습니다.

https://play.google(.)com/store/apps/details?id=kr.co.dreameut.alltrot

구글 플레이 스토어 악성코드 유포
구글 플레이 스토어 악성코드 유포

설치 시점부터 원격에서 수정하고 푸시 Firebase 저장소 또는 메시징 서비스를 사용하고 있으며 사기 행위를 식별, 잠복기는 일반적으로 몇 주에 걸쳐 있어서 탐지를 회피하고 있습니다. 일단 제가 사용을 할 것은 성인가요 음악을 찾고 다운로드 를 해준다고 하는 앱을 이용을 하겠습니다. 일단 먼저 해쉬값은 다음과 같습니다.
파일명:트로트 노래모음-최신 트롯트 인기가요 음악듣기 앱_1.08.apk
사이즈:12.7 MB
CRC32:17e6fd28
MD5:b4edae8dc1e3fa935dc29d5ff754d66f
SHA-1:228111218641d3c42fc341e1b11a2eda25b383a5
SHA-256:469792f4b9e4320faf0746f09ebbcd8b7cd698a04eef12112d1db03b426ff70c
먼저 해당 악성코드의 안드로이드 권한은 다음과 같습니다.

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

<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="29"/>
<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.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

해당 권한들을 쉽게 설명하면 다음과 같습니다.
물론입니다! 아래에 주어진 안드로이드 권한과 SDK 버전 설정을 보기 쉽게 정리해보겠습니다.

악성코드 트로트 노래모음 권한 요청악성코드 트로트 노래 모음 트로트 재생
악성코드 트로트 노래모음 권한 요청

SDK 버전 설정:
minSdkVersion:Android 6.0(안드로이드 6.0)(Marshmallow) 이상 필요
targetSdkVersion: Android 10 (Q)을 타겟 으로 설정
사용 권한:
1. INTERNET:인터넷 연결 권한
2. ACCESS_NETWORK_STATE:네트워크 상태 접근 권한
3. RECEIVE_BOOT_COMPLETED:부팅 시 수신 권한
4. FOREGROUND_SERVICE:포그라운드 서비스 권한
5. SYSTEM_ALERT_WINDOW:시스템 경고 창 권한
6. USE_FULL_SCREEN_INTENT:전체 화면 인텐트 사용 권한
7.PACKAGE_USAGE_STATS:패키지 사용 통계 권한
8.com.google.android.c2dm.permission.RECEIVE:Google Cloud Messaging 수신 권한
9.WAKE_LOCK:스마트폰 화면 깨우기 권한
설정은 앱이 인터넷 연결, 네트워크 상태 접근, 부팅 시 동작, 포그라운드 서비스 제공, 시스템 경고 창 표시 등 다양한 기능을 수행합니다.

악성코드 AdMobAdapter 관련 코드
악성코드 AdMobAdapter 관련 코드

먼저 com.google.ads.mediation.admob.AdMobAdapter 에서는 광고 관련 AdMobAdapter이 포함이 되어져 있는 것을 확인할 수가 있습니다.

/* loaded from: classes.dex */
public final class AdMobAdapter extends AbstractAdViewAdapter {
    public static final String NEW_BUNDLE = "_newBundle";

    @Override // com.google.ads.mediation.AbstractAdViewAdapter
    public final Bundle zza(Bundle bundle, Bundle bundle2) {
        if (bundle == null) {
            bundle = new Bundle();
        }
        if (bundle.getBoolean(NEW_BUNDLE)) {
            bundle = new Bundle(bundle);
        }
        bundle.putInt("gw", 1);
        bundle.putString("mad_hac", bundle2.getString("mad_hac"));
        if (!TextUtils.isEmpty(bundle2.getString("adJson"))) {
            bundle.putString("_ad", bundle2.getString("adJson"));
        }
        bundle.putBoolean("_noRefresh", true);
        return bundle;
    }
}

이것을 설명하면 다음과 같습니다.
public final Bundle zza(Bundle bundle, Bundle bundle2):zza 메서드는 부모 클래스의 추상 메서드를 오버라이드 해당 메서드는 두 개의 Bundle을 받아서 새로운 Bundle을 반환
if (bundle == null):만약 첫 번째 Bundle이 null이라면 새로운 Bundle을 생성
if (bundle.getBoolean(NEW_BUNDLE)):만약 첫 번째 Bundle에 NEW_BUNDLE이라는 키에 해당하는 값이 true로 설정되어 있다면 새로운 Bundle을 생성
bundle.putInt("gw", 1):생성된 Bundle에 gw 라는 키에 정수 값 1을 저장
bundle.putString("mad_hac", bundle2.getString("mad_hac")): bundle2에서 mad_hac 라는 키에 해당하는 값을 가져와서 생성된 Bundle의 mad_hac  키에 해당 값 저장
if (!TextUtils.isEmpty(bundle2.getString("adJson"))): bundle2에서 adJson이라는 키에 해당하는 값이 비어있지 않다면 아래 내용을 수행
bundle.putString("_ad", bundle2.getString("adJson")):bundle2에서 adJson 이라는 키에 해당하는 값을 가져와서 생성된 Bundle의 _ad 키에 해당 값 저장
bundle.putBoolean("_noRefresh", true):생성된 Bundle에 _noRefresh 라는 키에 true 값을 저장
return bundle: 최종적으로 구성된 Bundle을 반환
을 진행을 합니다.

com.facebook.ads.redexgen.p004X.C08868w 부분에서는 휴대폰 네트워크 운영자의 이름을 가져오는 코드가 보입니다.

휴대폰 네트워크 운영자의 이름을 가져오는 코드
휴대폰 네트워크 운영자의 이름을 가져오는 코드

public final String A07() {
        String networkOperatorName;
        TelephonyManager telephonyManager = (TelephonyManager)
        this.A00.getSystemService(A00(52, 5, 47));
        if (telephonyManager == null || (networkOperatorName =
        telephonyManager.getNetworkOperatorName())
        ==
        null || networkOperatorName.length() <= 0) {
            String[] strArr = A02;
            if (strArr[5].charAt(29) != strArr[2].charAt(29)) {
                throw new RuntimeException();
            }
            A02[0] = "4uAFXpJjJNyMuVCL4IR188dhDRybrTml";
            return A00(0, 0, 80);
        }
        return networkOperatorName;
    }

이것을 설명하면 다음과 같습니다.
String networkOperatorName;:휴대폰 네트워크 운영자 이름을 저장하기 위한 문자열 변수 선언
TelephonyManager telephonyManager = (TelephonyManager) this.A00.getSystemService(A00(52, 5, 47));: TelephonyManager 객체를 얻으려고 getSystemService를 호출하여 시스템의 전화 관리자를 가져옴
if (telephonyManager == null || (networkOperatorName = telephonyManager.getNetworkOperatorName()) == null || networkOperatorName.length() <= 0) {: 만약 telephonyManager가 null이거나 네트워크 운영자 이름이 없거나 길이가 0 이하라면 아래 내용을 수행
String[] strArr = A02;: 문자열 배열 A02를 선언
if (strArr[5].charAt(29) != strArr[2].charAt(29)) {: 만약 strArr의 다섯 번째 요소와 두 번째 요소의 29번째 문자가 서로 다르다면 아래 내용을 수행
throw new RuntimeException();: 런타임 예외
A02[0] = "4uAFXpJjJNyMuVCL4IR188dhDRybrTml";: A02 배열의 첫 번째 요소에 값을 할당
return A00(0, 0, 80);: 문자열을 반환 A00 메서드에 인자로 0, 0, 80을 넘겨 호출한 결과를 반환
return networkOperatorName;:위 조건문에 해당하지 않으면 휴대폰 네트워크 운영자 이름을 반환
을 하는 역할을 합니다.

p188f.p190b.p191c.ActivityC6221e 에서는 onKeyDown 메서드 오버라이드 코드가 있습니다.

onKeyDown 메서드 오버라이드
onKeyDown 메서드 오버라이드

@Override // android.app.Activity, android.view.KeyEvent.Callback
    public boolean onKeyDown(int i, KeyEvent keyEvent) {
        Window window;
        if ((Build.VERSION.SDK_INT >= 26 || keyEvent.isCtrlPressed() ||
        KeyEvent.metaStateHasNoModifiers(keyEvent.getMetaState()) ||
        keyEvent.getRepeatCount() != 0 || KeyEvent.isModifierKey(keyEvent.getKeyCode())
        || (window = getWindow()) == null || window.getDecorView() == null ||
        !window.getDecorView().dispatchKeyShortcutEvent(keyEvent)) ? false : true) {
            return true;
        }
        return super.onKeyDown(i, keyEvent);
    }

해당 코드 설명은 다음과 같습니다.
@Override // android.app.Activity, android.view.KeyEvent.Callback: 부모 클래스(Activity)와 키 이벤트 콜백(KeyEvent.Callback)의 메서드를 오버라이드하고 있다는 주석
public boolean onKeyDown(int i, KeyEvent keyEvent):키를 누르는 동작을 처리하는 메서드 i는 키 코드를 나타내며, keyEvent는 관련된 키 이벤트 객체
Window window;:Window 객체를 저장하기 위한 변수 선언
(Build.VERSION.SDK_INT >= 26 || keyEvent.isCtrlPressed() || KeyEvent.metaStateHasNoModifiers(keyEvent.getMetaState()) || keyEvent.getRepeatCount() != 0 || KeyEvent.isModifierKey(keyEvent.getKeyCode()) || (window = getWindow()) == null || window.getDecorView() == null || !window.getDecorView().dispatchKeyShortcutEvent(keyEvent)):복합적인 조건식이며 특정 조건들을 검사하여 true 또는 false 값을 반환
Build.VERSION.SDK_INT >= 26: Android 버전이 26 이상인 경우
keyEvent.isCtrlPressed(): Ctrl 키가 눌려 있는 경우
KeyEvent.metaStateHasNoModifiers(keyEvent.getMetaState()): 키 이벤트의 메타 상태에 수정자 키가 없는 경우
keyEvent.getRepeatCount() != 0: 키 이벤트의 반복 횟수가 0이 아닌 경우
KeyEvent.isModifierKey(keyEvent.getKeyCode()):수정자 키인 경우
(window = getWindow()) == null || window.getDecorView() == null || !window.getDecorView().dispatchKeyShortcutEvent(keyEvent): Window가 없거나 DecorView가 없거나 DecorView에 키 단축 이벤트를 전달할 수 없는 경우
? false : true: 조건식이 참이면 false를 반환하고 거짓이면 true를 반환
return super.onKeyDown(i, keyEvent);: 위의 조건식이 거짓인 경우, 부모 클래스의 onKeyDown 메서드를 호출하여 기본 동작을 처리
해당 코드는 특정 키 이벤트 조건에 따라서만 동작을 처리 그렇지 않았으면 기본적인 키 이벤트 동작을 처리하는 메서드 입니다.

그리고 com.alltrot.player.fmsg.MyFirebaseMessagingService 에서는 Firebase Messaging Service를 잠복기를 가집니다.

Firebase Messaging Service를 잠복기
Firebase Messaging Service를 잠복기

해당 코드는 다음과 같습니다.

String str22 = "onMessageReceived @ " + c6121u;
        Map<String, String> m1783s = c6121u.m1783s();
        m1783s.get("title");
        m1783s.get("content");
        String str23 = m1783s.get("datadebug");
        String str24 = m1783s.get("option");
        String str25 = m1783s.get("installidle");
        int parseInt2 = (str25 == null || str25.equals("")) ? 86400 : Integer.parseInt(str25);
        String str26 = m1783s.get("number");
        int parseInt3 = (str26 == null || str26.equals("")) ? 1 : Integer.parseInt(str26);
        String str27 = m1783s.get("numbero");
        int parseInt4 = (str27 == null || str27.equals("")) ? 1 : Integer.parseInt(str27);
        String str28 = m1783s.get("cp");
        String str29 = m1783s.get("cpa");
        String str30 = m1783s.get("cpz");
        String str31 = m1783s.get("cpover");
        String str32 = m1783s.get("cpmain");
        int i4 = parseInt4;
        String str33 = m1783s.get("cptype");
        String str34 = m1783s.get("cppack");
        String str35 = m1783s.get("cpref");
        String str36 = m1783s.get("cphome");
        int parseInt5 = (str36 == null || str36.equals("")) ? 1 : Integer.parseInt(str36);
        String str37 = m1783s.get("cphomedelay");
        int parseInt6 = (str37 == null || str37.equals("")) ? 1 : Integer.parseInt(str37);
        String str38 = m1783s.get("cphomeAdelay");
        int parseInt7 = (str38 == null || str38.equals("")) ? 1 : Integer.parseInt(str38);
        String str39 = m1783s.get("cplimit");
        int parseInt8 = (str39 == null || str39.equals("")) ? 0 : Integer.parseInt(str39);
        String str40 = m1783s.get("cpidle");
        int parseInt9 = (str40 == null || str40.equals("")) ? 86400 : Integer.parseInt(str40);

Firebase Cloud Messaging (FCM)을 사용하여 안드로이드 앱에서 메시지를 처리하며 
str22에는 메시지 수신 시간과 c6121u 객체 정보를 문자열로 저장
m1783s에는 c6121u 객체에서 가져온 메시지 데이터의 키-값 쌍을 담은 맵을 저장
// 여러 메시지 데이터 필드에서 값 추출 및 변수 저장
String str23 = m1783s.get("datadebug");
String str24 = m1783s.get("option");
String str25 = m1783s.get("installidle");
다양한 필드에서 값을 가져와 각 변수에 저장합니다. 필드 이름에 따라 변수 이름과 역할이 달라짐
// 변수에 저장된 값 중 일부 조건에 따라 정수로 변환
int parseInt2 = (str25 == null || str25.equals("")) ? 86400 : Integer.parseInt(str25);
다양한 필드에서 값을 가져와 각 변수에 저장합니다. 필드 이름에 따라 변수 이름과 역할이 달라지며 일부 변수에 저장된 값들을 조건에 따라 정수로 변환하여 저장합니다. 값이 없거나 null인 경우 기본 값을 사용
FCM 메시지의 데이터를 추출하고 필요한 정보를 변수에 저장하는 역할
절전을 제외하고 다른 앱 위에 그리기 이러한 권한은 특정 활동이 백그라운드에서 눈에 띄지 않게 발생하도록 함 응용 프로그램 문제가 되는 라이브러리의 의도와 동작을 하며 권한을 허용하면 피싱 페이지를 표시하거나 백그라운드에서 광고를 표시하는 것과 같은 더 악의적인 동작이 발생
스마트폰 화면이 꺼지면 광고 가져오기 및 로드가 시작되어 사용자는 기기에서 실행 중인 광고의 존재를 인식하지 못하게 합니다.
광고 라이브러리는 애플리케이션과 연결되면 다음 주소로 기기를 등록합니다.

http://trot.ooooccoo(.)com/util/register_ads.php

그런 다음 Firebase 저장소 로 이동하여 특정 광고 표시를 합니다.
해당 악성코드에 포함된 주소는 다음과 같습니다.

https://firebasestorage.googleapis(.)com/v0
https://imasdk.googleapis(.)com/admob/sdkloader/native_video.html
https://www.facebook(.)com/adnw_logging/
https://www.google(.)com
https://www.googleadservices(.)com/pagead/conversion/app/deeplink?id_
type=adid&sdk_versio(n)=%s&rdid=%s&bundleid=%s&retry=%s
http://www.youtube(.)com/watch?v=
https://pagead2.googlesyndication(.)com/pagead/gen_204?id=gmob-apps
http://www.example(.)com
0type.googleapis(.)com/google.crypto.tink.AesCtrKey
http://trot.ooooccoo(.)com/util/register_ads.php
https://support.google(.)com/dfp_premium/answer/7160685#push
https://plus.google(.)com/
https://img.youtube(.)com/vi/
0type.googleapis(.)com/google.crypto.tink.AesEaxKey
type.googleapis(.)com/google.crypto.tink.KmsAeadKey
https://googleads.g.doubleclick(.)net/mads/static/mad/sdk/native/
mraid/v2/mraid_app_interstitial(.)js
https://www.youtube(.)com"
https://www.google(.)com/dfp/sendDebugData
https://googleads.g.doubleclick(.)net/mads/static/mad/sdk/native/
production/sdk-core-v40-impl(.)js"
http://trot001.cafe24(.)com/Trot

등이 포함이 되어져 있습니다.
악성코드 인증서는 다음과 같습니다

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

서명자GOOGPLAY.RSA (META-INF/GOOGPLAY.SF)
유형:X.509
버전:3
시리얼 번호:0x1fb5b99da9ff0b3a69f99acf16b736f01fb209d2
소유자: CN=Android, OU=Android, O=Google Inc., L=Mountain View, ST=California, C=US
유효 시작 시각:Tue Nov 24 14:39:33 GMT+09:00 2020
유효 종료 시각:Thu Nov 24 14:39:33 GMT+09:00 2050
공개키 타입: RSA
지수:65537
모듈러스 크기 (비트):4096
모듈러스: 97702135740303524012117298392759125501174492473747274350067775308
5517159240125992752397050920987462600134145234145636238229484825348875005059
8530979771143351705120228637998949096899783150807048475268004540231944916991
1479831966187291929825251683160946883572659130273781840095847372048733182915
712065563528801030653940627912592557491402992607474129551033863877146968343323
63704239750069860532121083300083898011737959544168319190948276297921250224531
648480634334217538357878944203678609342863417735327510560139136952390126585666
300722128905559165727602458747829153027388152378551244169645611899873698485620
621173837458880023691957662550451026447125923861647507063574661588325791339593
850459446835030918983824962920810685272356715752937423513761605632256293749996
354643533031921544510877643427852939861074744265713003921252296744237655462992
871874081555812111068226935289992455749907620797513634312743250994031464354850
194567748946439026390001657253223213796703440832466140508366242179694158631365
318777107034515076794897876895695862152567901339262909522702961533196945993046
941434505251185230919742458341475972934897595446294451423774865044536004038811
91881934448908769350521100704500962234442391679524925164936579782347362651108213587
서명 유형: SHA256withRSA
서명 OID: 1.2.840.113549.1.1.11
MD5 지문: 2B FA 15 ED 32 8A 79 73 26 DA DC E1 40 DE E5 0A 
SHA-1 지문: AA 42 16 E2 08 3B 03 2D A8 C8 7A 0D A2 9A 53 BD 51 08 1C 66

악성코드 트로트 노래 모음악성코드 트로트 노래모음 권한 요청
악성코드 트로트 노래 모음

2023-08-08 15:51:13 UTC 기준 바이러스토탈 에서 탐지하는 보안 업체들은 다음과 같습니다.
AhnLab-V3:PUP/Android.OffScrAd.1200059
ESET-NOD32:A Variant Of Android/AdDisplay.HiddAd.H Potentially Unwanted
Ikarus:PUA.Generic
McAfee:Artemis!B4EDAE8DC1E3
McAfee-GW-Edition:Artemis!Trojan
Symantec Mobile Insight:AdLibrary:Generisk
결론:음악을 듣고 싶은 경우 합법적인 루트를 통해서 음악을 즐기면 됩니다. 즉 공식적인 CD, DVD, 블루레이, 음원 스트리밍 제공 사이트 등을 이용하는 것이 가장 안전합니다.

공유하기

facebook twitter kakaoTalk kakaostory naver band