QL 상세 분석 보고서

QL 악성앱 분석 보고서

분석 버전: v3.0 (ML Gold Standard Upgrade)

분석일: 2025-11-14 ~ 2026-02-14 (Gold Standard 심층 분석)

분석기관: 주식회사 아크링크

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

1. 앱 기본 정보

항목 · 값

파일명 · QL.apk

표면 패키지명 · com.vtdxcvn.xrrdvjk

내부 패키지 · com.cryonix.wavegrid

앱 패밀리 · Wavegrid Core (Variant B+)

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

compileSdkVersion · 34 (Android 14)

위협 수준 · **HIGH**

우회 상태 · **성공**

Application 클래스 · com.cryonix.wavegrid.Myapplication

smali 파일 수 · 9,204개 (smali: 7,629 + smali_classes2: 1,575)

소스 언어 · Kotlin (.kt) + Java (.java) 혼합

앱 위장 전략

· 앱 이름 "QL"로 간결한 유틸리티/메신저 앱을 위장

· 표면 패키지명(com.vtdxcvn.xrrdvjk)은 랜덤 문자열로 탐지 회피

· 자음 연속 패턴(vtdxcvn, xrrdvjk) = 자동 생성 도구 흔적

· 내부 실제 코드는 com.cryonix.wavegrid 네임스페이스 사용

· QN과 동일 인프라 계열 (dasj234jnaftad5.* 도메인)

· 아이콘은 @mipmap/applogo로 합법 앱처럼 위장

Variant B+ 식별 근거

QL은 Wavegrid Core 계열의 진화형(Variant B+) 으로 분류된다:

구분 · Variant A · Variant B · **Variant B+ (QL)**

네임스페이스 · com.tushiweih.wavegrid · com.cryonix.wavegrid · com.cryonix.wavegrid

API 패키지 · api/ · api/ · **server/**

Application · MyApp · MyApp · **Myapplication**

메인 Activity · HomeActivity · HomeActivity · **SendInfoActivity**

화면 전환 · 직접 호출 · 직접 호출 · **Kotlin 코루틴**

이미지 전송 · Luban 압축 · Luban 압축 · **Luban + Thread**

대표 앱 · GgulGgulTalk, LOVENSES_2 · Lovee, LOVEY · **QL, QN**

2. 권한 분석

AndroidManifest.xml 선언 권한

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

1 · `READ_CONTACTS` · **CRITICAL** · [피해자 정보 비공개] 전체 탈취 → 지인 대상 2차 피싱 공격

2 · `READ_PHONE_STATE` · **HIGH** · 기기 IMEI, 전화번호 수집 → [피해자 정보 비공개] 및 추적

3 · `READ_PHONE_NUMBERS` · **HIGH** · [피해자 정보 비공개] 직접 수집 → 본인인증 악용

4 · `READ_MEDIA_IMAGES` · **HIGH** · 갤러리 이미지 접근 → 사진/신분증 탈취

5 · `READ_MEDIA_VIDEO` · **HIGH** · 동영상 접근 → 사생활 영상 탈취 및 협박

6 · `READ_EXTERNAL_STORAGE` · **MEDIUM** · Android 12 이하 전체 저장소 접근 (maxSdkVersion=32)

7 · `INTERNET` · **LOW** · 네트워크 통신 (C2 서버 데이터 전송용)

8 · `DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION` · **INFO** · 자체 서명 권한 (내부 브로드캐스트 수신기 보호)

미선언 권한 (코드에 존재하나 Manifest 미등록)

· READ_SMS: PermissionConstants(p51.smali)에 "SMS" 그룹이 정의되어 있으나, AndroidManifest.xml에 선언되지 않음

· CAMERA: PermissionConstants에 정의되어 있으나 선언되지 않음

· 이는 라이브러리(p51/r51)가 모든 Android 권한을 매핑하는 범용 코드이며, 실제 앱은 일부만 사용

런타임 권한 요청 흐름 (SendInfoActivity.g0 메서드)

```

g0() 메서드 권한 요청 로직:

1. SDK_INT >= 0x21 (33) 확인

→ true: READ_MEDIA_IMAGES + READ_MEDIA_VIDEO 요청

→ false: READ_EXTERNAL_STORAGE 요청 (하위 호환)

2. READ_CONTACTS 추가

3. READ_PHONE_NUMBERS 추가

4. READ_PHONE_STATE 추가

5. PermissionX 프레임워크(s51)로 일괄 요청

```

특징: SDK 33 분기를 통해 Android 13+ 미디어 세분화 권한과 이전 버전 스토리지 권한을 자동 분기. 이는 최신 Android에서도 동작하도록 설계된 증거.

3. 코드 구조 분석

핵심 클래스 구조

```

com.cryonix.wavegrid/

├── Myapplication.smali # Application 클래스 (EasyHttp 초기화)

├── server/ # ※ api/ 대신 server/ 패키지 (진화형)

│ ├── LoginApi.smali # 로그인 API (webapp/login)

│ ├── ContactApi.smali # 연락처 전송 API (webapp/saveAddressBook)

│ ├── HttpData.smali # HTTP 응답 래퍼 (code, data, msg)

│ └── HttpLifecycleManager.smali # HTTP 생명주기 관리

├── bean/

│ ├── CollectBeanDTO.smali # 연락처 수집 DTO (name, number)

│ ├── InfoBeanDTO.smali # 이미지/연락처 정보 DTO (path, displayName, content, date, type)

│ └── ResultSuccessCallBack.smali # 이미지 업로드 성공 콜백

└── view/

├── LoginActivity.smali # 로그인 (초대코드 검증)

├── LoginActivity$goToNext$2.smali # 코루틴 기반 HTTP 콜백

├── SendInfoActivity.smali # 데이터 탈취 메인 (HomeActivity 대체)

├── SendInfoActivity$a.smali # 이미지 압축 후 업로드 핸들러

├── SendInfoActivity$startNextInfo$1.smali # 연락처 전송 HTTP 콜백

└── *_ViewBinding.smali # ButterKnife 뷰바인딩

난독화된 유틸리티 (smali 루트):

├── gq1.smali # StatusUtils.java - 데이터 수집 유틸리티

├── ml1.smali # ServerInfo.kt - C2 URL 상수 + 이미지 업로드

├── bg1.smali # RequestServer.java - HTTP 클라이언트 설정

├── f90.smali # HttpsUtils.java - SSL bypass (이미지 전용)

├── f90$d.smali # HttpsUtils 내부 TrustManager (빈 구현)

├── mi1.smali # DataStore 래퍼 (IFFIRST, ISUPLOAD, INVITECODE 등)

├── r51.smali # PermissionMap.kt - 권한 매핑

├── p51.smali # PermissionConstants.java - 권한 상수

└── s51.smali # PermissionX 프레임워크

라이브러리 (패키지 유지):

├── com/hjq/http/ssl/HttpSslFactory.smali # SSL 팩토리 (일반 API용)

├── com/hjq/http/ssl/UnSafeTrustManager.smali # TrustAll X509 구현

├── com/hjq/http/ssl/UnSafeHostnameVerifier.smali # 호스트명 무시

├── com/hjq/http/ssl/HttpSslConfig.smali # SSL 설정 래퍼

└── com/hjq/http/EasyHttp*.smali # EasyHttp 프레임워크

```

앱 실행 흐름 (Variant B+ 진화형)

```

Myapplication.onCreate()

→ OkHttpClient 생성

→ EasyConfig.setServer(bg1) # C2 URL + SSL bypass 설정

→ EasyConfig.setHandler(wf1) # 응답 핸들러 설정

LoginActivity.onCreate()

→ setContentView(0x7f0b001c) # 로그인 레이아웃

→ ButterKnife.bind() # 뷰바인딩 (codeNumber, startBtn, loading)

→ L() 호출

→ [패치됨] l0() 직접 호출 → SendInfoActivity로 이동

→ [원본] IFFIRST 확인 → UUIDVALUE 복원 → 초대코드 검증

SendInfoActivity.onCreate()

→ setContentView(0x7f0b001d)

→ ButterKnife.bind() # btnSync 바인딩

→ L() 호출

→ INVITECODE 로드 → N 필드에 저장

→ ISUPLOAD 확인 → btnSync 활성화/비활성화

사용자가 btnSync 클릭 시:

→ onButtonClick() → g0() → 권한 요청

→ 권한 승인 시 h0() → j0() 호출

→ p0(): 연락처 수집 → saveAddressBook API 전송

→ o0(): 이미지 수집 → Luban 압축 → Thread에서 업로드

→ 전송 성공 시 ISUPLOAD=true 설정 → btnSync 비활성화

```

4. 네트워크/C2 분석

C2 서버 정보

항목 · 값

도메인 · [비공개]

프로토콜 · HTTPS (SSL Bypass 적용)

Base URL · `[비공개]

서버 프레임워크 · RuoYi (若依) Spring Boot

TLD · `.space` (저가 TLD - 악성 도메인에 빈번 사용)

인프라 그룹 · dasj234jnaftad5.*

C2 URL 하드코딩 위치 (이중 정의)

ml1.smali (ServerInfo.kt) - 정적 상수:

```smali

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

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

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

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

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

```

bg1.smali (RequestServer.java) - getHost() 메서드:

```smali

.method public getHost()Ljava/lang/String;

const-string p0, "[비공개]

return-object p0

.end method

```

ml1.a() 메서드 - 이미지 업로드 시 동적 URL 구성:

```smali

const-string v2, "[비공개]

invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(...)

invoke-virtual {v1, p2}, Ljava/lang/StringBuilder;->append(...) # "webapp/image/{userName}"

```

C2 URL이 3곳에 중복 하드코딩되어 있다. ml1은 상수로, bg1은 인터페이스 구현으로, ml1.a()는 리터럴로 각각 사용.

API 엔드포인트

# · 메서드 · 엔드포인트 · 목적 · 전송 데이터

1 · POST · `[비공개] · 초대코드 로그인 · cId(초대코드), name(UUID)

2 · POST · `[비공개] · 연락처 탈취 · userName, phoneContent(JSON), bjPhone, model, operatorName

3 · POST · `[비공개] · 이미지 탈취 · file(multipart/form-data), other_field="value"

API 요청 상세 분석

Login API (LoginApi.smali):

```json

{

"cId": "초대코드 (EditText에서 입력)",

"name": "UUID.randomUUID() 자동 생성"

}

```

· 서버 응답 code=200 시: UUIDVALUE와 INVITECODE를 DataStore에 저장 후 SendInfoActivity로 이동

· 서버 응답 code!=200 시: result.msg를 Toast로 표시

ContactApi (ContactApi.smali):

```json

{

> [비공개]

"phoneContent": "[{\"name\":\"홍길동\",\"number\":\"010-**-**\"}, ...]",

"bjPhone": "TelephonyManager.getLine1Number()",

"model": "android",

"operatorName": "TelephonyManager.getSimOperatorName()"

}

```

Image Upload (ml1.a 메서드):

```

POST [비공개]

Content-Type: multipart/form-data

{

"file": <이미지 파일 (Luban 압축 후)>,

"other_field": "value"

}

```

TLD 분석

· .space TLD는 저가 도메인으로 악성 인프라에 빈번 사용

· dasj234jnaftad5 접두사가 QN([비공개])과 100% 동일 → 같은 운영 그룹

· 랜덤 문자열 도메인명은 자동 생성 도구 사용 흔적

인프라 관계 맵

```

dasj234jnaftad5 인프라 그룹 (Variant B+ 전용):

├── [비공개] → QL (본 앱)

└── [비공개] → QN (자매 앱)

동일 개발팀 추정 근거:

├── 동일 도메인 접두사 (dasj234jnaftad5)

├── 동일 코드 구조 (Variant B+)

├── 동일 API 엔드포인트 패턴 ([비공개])

├── 동일 서버 프레임워크 (RuoYi)

└── TLD만 변경 (.space ↔ .buzz)

```

5. 방어기제 분석

# · 방어기제 · 구현 방식 · 상태 · 우회 방법

1 · 초대코드 검증 · LoginActivity L() → goToNext$2 코루틴 서버 검증 · **우회됨** · L()에서 l0() 직접 호출

2 · IFFIRST 플래그 · DataStore 기반 재방문 감지 · **우회됨** · L() 패치로 무시됨

3 · ISUPLOAD 플래그 · 1회 전송 후 버튼 비활성화 · 분석됨 · startNextInfo$1 콜백에서 설정

4 · minSdkVersion 29 · API 29+ 전용 메서드 사용 · **우회됨** · API 29+ 메서드 제거

5 · 패키지명 위장 · 표면/내부 패키지명 이중 구조 · 분석됨 · manifest vs smali 대조

6 · 난독화 · ProGuard/R8 클래스명 축약 · 분석됨 · smali 수준 역추적

7 · Kotlin 코루틴 · 비동기 검증 로직 (goToNext$2) · 분석됨 · 코루틴 내부 콜백 추적

8 · SSL Bypass · 이중 경로 SSL 인증서 무시 · 분석됨 · 아래 섹션 10에서 상세 분석

초대코드 검증 원본 흐름 (패치 전)

```

LoginActivity.L() 원본:

1. mi1.f("IFFIRST", false) → 기존 방문 여부 확인

2. IFFIRST=true → l0() 호출 (이미 인증된 사용자)

3. IFFIRST=false → mi1.r("UUIDVALUE", "") → 저장된 UUID 복원

4. UUID를 M 필드에 저장

사용자가 startBtn 클릭 시:

5. EditText에서 초대코드 읽기

6. TextUtils.isEmpty() 검증 → 비어있으면 Toast

7. M(UUID)가 비어있으면 UUID.randomUUID() 새 생성

8. f0(uuid, inviteCode) → EasyHttp.post(LoginApi) → goToNext$2

goToNext$2.onHttpSuccess():

9. result.code == 200 → UUIDVALUE/INVITECODE 저장 → l0() 호출

10. result.code != 200 → result.msg Toast 표시

```

패치 적용 내용

```smali

LoginActivity.L() - 패치 후

PATCHED: Bypass invitation code requirement

invoke-virtual {p0}, Lcom/cryonix/wavegrid/view/LoginActivity;->l0()V

return-void

```

에뮬레이터/루팅 감지

· 별도 에뮬레이터 감지 코드 없음: Wavegrid Core 계열은 에뮬레이터/루팅 감지를 구현하지 않음

· 방어기제가 초대코드 검증에 집중

6. 데이터 탈취 분석

수집 대상 데이터

# · 데이터 유형 · 수집 방법 · 전송 엔드포인트 · 위험도

1 · 연락처 전체 · ContactsContract.Contacts + Phone · [비공개] · **CRITICAL**

2 · 이미지/사진 · MediaStore.Images.Media · [비공개] · **HIGH**

3 · 전화번호 · TelephonyManager · saveAddressBook API (bjPhone 필드) · **HIGH**

4 · 기기 모델명 · 하드코딩 "android" · saveAddressBook API (model 필드) · **MEDIUM**

5 · 통신사 정보 · TelephonyManager.getSimOperatorName() · saveAddressBook API (operatorName 필드) · **MEDIUM**

전송 빈도

· 1회성 전송: ISUPLOAD 플래그 기반. 권한 획득 직후 연락처 + 이미지를 1회 일괄 전송

· 전송 성공 시 ISUPLOAD=true 저장 → btnSync 비활성화

· 앱 재실행 시 ISUPLOAD 확인 → 이미 전송했으면 버튼 비활성 상태 유지

model 필드 하드코딩 분석

```smali

SendInfoActivity.p0() 라인 6

const-string v3, "android"

iput-object v3, v2, Lcom/cryonix/wavegrid/server/ContactApi;->model:Ljava/lang/String;

```

model 필드가 "android"로 하드코딩되어 있다. 이는 Wavegrid 패밀리 전체에서 공통으로 나타나는 특징이며, 실제 기기 모델(Build.MODEL)을 전송하지 않는다. 개발자가 서버 측에서 Android/iOS를 구분하기 위한 플랫폼 식별자로 사용하는 것으로 추정된다.

7. 우회 결과

적용 패치 목록

# · 패치 대상 · 패치 내용 · 결과

1 · LoginActivity.smali L() · 원본 IFFIRST/UUIDVALUE 로직 제거 → l0() 직접 호출 · 초대코드 검증 우회

2 · AndroidManifest.xml · `appComponentFactory` 속성 제거 · Android 9 호환성 확보

3 · AndroidManifest.xml · API 29+ 전용 컴포넌트 제거/주석 처리 · Android 9 호환성 확보

패치 결과

· 초대코드 없이 SendInfoActivity까지 진입 가능

· 권한 요청 단계까지 도달하여 전체 동작 흐름 분석 완료

· 패치된 APK: apk/260109_QL/260109_QL_signed_patched.apk (최신 버전)

8. StatusUtils(gq1.smali) 전수 분석

Variant B+ StatusUtils 핵심 차이점

기존 Variant A (ew1.smali, 9 메서드):

```

a: compressImage (이미지 압축)

b: getAlbum (갤러리 수집)

c: getContact (연락처 간이)

d: getContacts (연락처 상세)

e: getContactss (오타 중복)

f: getImg (이미지 URI 변환)

g: getMsgs (SMS 수집)

h: getOperatorName (통신사)

i: saveFile (파일 저장)

```

Variant B+ (gq1.smali, 4 메서드):

```

a: getAlbum (갤러리 수집)

b: getContact (연락처 간이)

c: getContacts (연락처 상세) → 실제 사용: p0()에서 호출 (saveAddressBook)

d: getOperatorName (통신사)

```

진화 분석: Variant B+는 StatusUtils를 4개 메서드로 리팩토링했다:

1. compressImage 제거 → Luban 라이브러리로 대체 (SendInfoActivity$a에서 직접 처리)

2. getContactss 제거 → 오타 함수 정리

3. getMsgs 제거 → SMS 수집 기능 완전 제거

4. getImg 제거 → MediaStore 직접 접근으로 간소화

5. saveFile 제거 → 불필요한 유틸리티 정리

이는 개발자가 코드를 정리(refactoring) 한 흔적이며, Variant B+가 A보다 후기 버전임을 입증한다.

gq1.a() - getAlbum 분석

```

MediaStore.Images.Media.EXTERNAL_CONTENT_URI 쿼리

→ Cursor에서 _id, _display_name, _data 추출

→ InfoBeanDTO(path=_data) 리스트 반환

→ SendInfoActivity.o0()에서 Luban 압축 후 업로드

```

gq1.b() - getContact 분석

```

ContactsContract.CommonDataKinds.Phone.CONTENT_URI 쿼리

→ Cursor에서 display_name, data1(번호), contact_id 추출

→ InfoBeanDTO(displayName, content) 리스트 반환

→ 참조용 (실제 전송은 gq1.c 사용)

```

gq1.c() - getContacts 분석 (실제 전송에 사용)

```

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

→ _id, display_name, has_phone_number 추출

2단계: has_phone_number > 0 인 연락처만 선별

→ ContactsContract.CommonDataKinds.Phone.CONTENT_URI에서 contact_id= 조건 쿼리

→ data1(번호)에서 공백/하이픈 제거: trim() → replace(" ","") → replace("-","")

→ CollectBeanDTO(name, number) 리스트 반환

3단계: SendInfoActivity.p0()에서 Gson.toJson(list)로 직렬화

→ ContactApi.phoneContent에 JSON 문자열로 전송

```

번호 정규화: 전화번호에서 공백과 하이픈을 제거하여 순수 숫자만 전송. 이는 서버에서 번호 검색/매칭을 용이하게 하기 위한 설계.

gq1.d() - getOperatorName 분석

```

TelephonyManager.getSimOperatorName() 호출

→ SIM 카드의 통신사 이름 반환 (예: "SK Telecom", "KT", "LG U+")

→ ContactApi.operatorName 필드로 전송

```

9. SMS 코드 부재 분석

QL의 SMS 관련 코드 상태

Variant A(LOVENSES_2 등)의 StatusUtils에는 getMsgs() 메서드가 존재하여 SMS 수집 코드가 데드 코드로 남아있었다. 하지만 QL(Variant B+)에서는:

1. StatusUtils에서 getMsgs 완전 제거: gq1.smali에 SMS 관련 메서드 없음

2. AndroidManifest에 READ_SMS 미선언: 권한 자체가 없음

3. content://sms/ 접근 코드 없음: Grep 검색 결과, SMS 관련 파일은 r51.smali(PermissionMap)과 p51.smali(PermissionConstants)의 라이브러리 상수 정의뿐

r51.smali (PermissionMap.kt) 내 SMS 관련:

```smali

라인 392-417: SMS 권한 그룹 매핑 (라이브러리 범용 정의)

const-string v10, "android.permission.SEND_SMS"

const-string v12, "android.permission-group.SMS"

... RECEIVE_SMS, READ_SMS, RECEIVE_WAP_PUSH, RECEIVE_MMS

```

이들은 PermissionX 라이브러리의 권한 매핑 테이블로, 실제 앱에서 SMS 권한을 요청하지 않는다. 앱 코드(com.cryonix.wavegrid)에서는 SMS를 일절 참조하지 않는다.

Variant A vs B+ SMS 비교

항목 · Variant A (LOVENSES_2) · **Variant B+ (QL)**

getMsgs 메서드 · 존재 (데드 코드) · **완전 제거**

content://sms/ 접근 · 코드 존재 (미호출) · **코드 없음**

READ_SMS 선언 · Manifest 미선언 · Manifest 미선언

SMS 안전 레이어 · 4중 (Manifest + 런타임 + 미호출 + API 없음) · **2중 (Manifest + 코드 없음)**

결론: QL은 SMS 수집 기능을 코드 수준에서 완전히 제거했다. Variant A에서 데드 코드로 남아있던 getMsgs가 리팩토링 과정에서 정리된 것이다.

10. SSL 우회 체인 분석

이중 경로 SSL Bypass 아키텍처

QL은 두 가지 독립적인 SSL 인증서 검증 우회 경로를 가진다:

경로 1: 일반 API 통신 (EasyHttp 경유)

```

Myapplication.onCreate()

→ EasyConfig.setServer(bg1)

→ bg1.getOkHttpClient()

→ HttpSslFactory.generateSslConfig() [1]

→ generateSslConfigBase(null, null, null, [])

→ prepareTrustManager([]) → null (빈 배열)

→ p0 == null && p2 == null

→ new UnSafeTrustManager() [2]

→ SSLContext.init([UnSafeTrustManager])

→ HttpSslConfig(socketFactory, trustManager)

→ HttpSslFactory.generateUnSafeHostnameVerifier() [3]

→ new UnSafeHostnameVerifier()

→ OkHttpClient.Builder

.sslSocketFactory(factory, UnSafeTrustManager)

.hostnameVerifier(UnSafeHostnameVerifier)

.build()

```

핵심 파일:

· com/hjq/http/ssl/HttpSslFactory.smali - SSL 팩토리 (5개 오버로드 메서드)

· com/hjq/http/ssl/UnSafeTrustManager.smali - 빈 checkServerTrusted

· com/hjq/http/ssl/UnSafeHostnameVerifier.smali - verify() always true

· com/hjq/http/ssl/HttpSslConfig.smali - 설정 래퍼

경로 2: 이미지 업로드 (OkHttp 직접 사용)

```

SendInfoActivity$a.f()

→ ml1.a(activity, url, file, callback)

→ f90.c(null, null, null) [1]

→ f90.e(null) → null (빈 입력)

→ p1 == null

→ new f90$d() [2]

→ SSLContext.init([f90$d TrustManager])

→ f90$c(socketFactory, trustManager)

→ OkHttpClient.Builder

.sslSocketFactory(factory, f90$d)

.build() [3]

→ MultipartBody로 이미지 전송

```

핵심 파일:

· f90.smali (HttpsUtils.java) - 이미지 전용 SSL 팩토리

· f90$d.smali - 빈 X509TrustManager (checkServerTrusted = return-void)

· f90$c.smali - SSL 설정 래퍼

· f90$b.smali - TrustManager 위임자

SSL Bypass 상세 분석

UnSafeTrustManager.smali:

```smali

.method public checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V

.annotation build Landroid/annotation/SuppressLint;

value = { "TrustAllX509TrustManager" }

.end annotation

return-void # 어떤 인증서든 신뢰

.end method

```

UnSafeHostnameVerifier.smali:

```smali

.method public verify(Ljava/lang/String;Ljavax/net/ssl/SSLSession;)Z

.annotation build Landroid/annotation/SuppressLint;

value = { "BadHostnameVerifier" }

.end annotation

const/4 p0, 0x1 # 항상 true

return p0

.end method

```

f90$d.smali (HttpsUtils 내부 TrustManager):

```smali

.method public checkServerTrusted(...)V

return-void # 어떤 인증서든 신뢰

.end method

.method public getAcceptedIssuers()[Ljava/security/cert/X509Certificate;

const/4 p0, 0x0

new-array p0, p0, [Ljava/security/cert/X509Certificate;

return-object p0 # 빈 배열 반환

.end method

```

경로 1 vs 경로 2 비교

항목 · 경로 1 (일반 API) · 경로 2 (이미지 업로드)

사용처 · login, saveAddressBook · webapp/image/{userName}

HTTP 클라이언트 · EasyHttp (com.hjq.http) · OkHttp3 직접 사용

TrustManager · UnSafeTrustManager · f90$d (HttpsUtils 내부)

HostnameVerifier · UnSafeHostnameVerifier · **없음** (기본값 사용)

난독화 · 패키지명 유지 · f90으로 축약

@SuppressLint · 있음 · 없음

보안 취약점: 두 경로 모두 TLS 인증서 검증을 완전히 무력화한다. 이는 C2 서버가 자체 서명 인증서를 사용하거나, 도메인이 빈번히 변경되어 유효한 인증서를 유지하기 어려운 상황을 반영한다.

11. 교차 비교 분석

Wavegrid Core 패밀리 전체 비교

앱 · 변이 · 네임스페이스 · StatusUtils · 메서드 수 · SMS · 이미지 · server/ · 코루틴

GgulGgulTalk · A · tushiweih · ew1 · 9 · 데드 · Luban · x · x

LOVENSES_2 · A · tushiweih · ew1 · 9 · 데드 · Luban · x · x

Lovee · B · cryonix · ew1 · 9 · 데드 · Luban · x · x

LOVEY · B · cryonix · ew1 · 9 · 데드 · Luban · x · x

**QL** · **B+** · **cryonix** · **gq1** · **4** · **제거** · **Thread** · **o** · **o**

QN · B+ · cryonix · gq1 · 4 · 제거 · Thread · o · o

QL vs QN 상세 비교

항목 · QL · QN

패키지명 (표면) · com.vtdxcvn.xrrdvjk · com.oxygzfnfj.hngtrte

패키지명 (내부) · com.cryonix.wavegrid · com.cryonix.wavegrid

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

TLD · .space · .buzz

StatusUtils · gq1.smali · gq1.smali

ServerInfo · ml1.smali · ml1.sm

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