Flying Eagle RAT 심층 분석

Post image

안녕하세요. 분석팀의 Jiyong입니다.

2026년 6월, 중국 CCTV는 공안기관을 사칭한 악성 앱에 주의하라는 경고를 전했습니다. 공식 서비스를 제공하는 것처럼 사용자를 속여 설치를 유도한 뒤 결제정보를 탈취하고 스마트폰을 원격 제어할 수 있다는 내용이었습니다.

이후 보안 기업 hunt.io와 독립 연구자의 조사에서 이 앱과 Flying Eagle(飞鹰) Android RAT 프레임워크의 연관성이 드러났습니다. 조사에서는 170개의 관련 서버를 식별하고 Telegram을 통한 소스코드 유통 경로를 추적했습니다. APK를 생성하는 빌더의 내부 구조와 난독화 방식도 소개했습니다.

저는 이 보고서를 읽으며 빌더가 만들어 낸 APK 내부가 궁금해졌습니다. 설치된 앱은 어떤 방식으로 사용자를 속이고 스마트폰을 조작할까요? 보고서에서 소개한 기능들은 실제 코드에서 어떻게 연결되어 있을까요?

디컴파일러로 코드를 따라가자 제조사에 맞는 가짜 시스템 업데이트 화면을 선택하는 구조가 드러났습니다. 정상적인 업데이트처럼 보이는 화면으로 사용자가 기다리도록 유도하는 한편, 접근성 서비스를 이용해 화면 녹화 승인을 시도하거나 암호화폐 앱의 입력값을 수집하는 코드도 확인할 수 있었습니다.

이번 분석 글은 배포 경로나 인프라보다 빌드된 APK 자체의 코드 분석에 집중합니다. 암호화된 에셋을 복호화해 확인한 가짜 화면부터 제조사별 화면 조작, C2 통신과 원격 제어, 암호화폐 거래소·지갑의 자격증명 수집까지 살펴보겠습니다. 각 기능의 구현을 따라가며 코드에서 확인할 수 있는 동작과 실제 실행 검증이 필요한 부분을 구분해 보겠습니다.

개요

Flying Eagle 프레임워크

Flying Eagle은 APK 빌더와 C2 관리 패널을 하나로 묶은 중국어 기반 Android RAT 프레임워크입니다. 공격자는 이 프레임워크를 이용해 공안국, 금융, 성인 스트리밍 등의 앱으로 위장된 악성 APK를 생성하고, 감염된 기기를 웹 패널에서 실시간으로 원격 제어할 수 있습니다.

hunt.io 리포트에 따르면 2026년 2월에는 Flying Eagle 소스코드 유출을 언급하는 게시물이 등장했으며, 고객 서버와 데이터베이스 침해를 주장하는 대화도 공개되었습니다. 유출된 소스코드는 Telegram 채널을 통해 빠르게 유통되었고, 이후 Docker 원클릭 배포 패키지(中国龙.zip, 388MB)까지 등장하면서 기술적 진입 장벽이 크게 낮아졌습니다. 유출된 코드를 수정한 버전들이 유통되고 있으며, 별도로 개발된 후속 플랫폼으로 추정되는 Night Dragon(夜龙)도 등장했습니다. 또한, hunt.io는 이와 관련된 활성 C2 서버를 170개 이상 식별한 바 있습니다.

분석 대상 앱

이번 분석에 사용한 앱은 hunt.io 리포트에서 공개된 IOC 중 하나로, 공안국 앱으로 위장하여 배포된 APK입니다.

항목
파일명 autoclicker_pro.apk
패키지명 com.gameclicker.autoclicker.pro
버전 7.1.6 stager
SHA-256 c692ad120cc90548d48dbe57d006f2403c49833b8993af3c38fe031eb39999bd
서명 AOSP testkey (android@android.com, 2008-2035)
Min SDK 24 (Android 7.0)
Target SDK 34 (Android 14)

[표 1] 분석 대상 정보

서명 정보에서 눈에 띄는 것은 AOSP testkey로 서명되어 있다는 점입니다. 이는 Android 오픈소스 프로젝트에서 사용하는 테스트용 키이므로, 일반적인 상용 앱의 배포 서명과는 성격이 다릅니다. 이 샘플에서는 빌더 산출물과 일치하는 식별 단서로 볼 수 있지만, 서명 정보만으로 Flying Eagle 빌더 사용을 단정할 수는 없습니다.

또한 앱 이름과 패키지명이 “Auto Clicker"를 표방하고 있지만 이는 빌더가 패키지명을 랜덤 조합하여 생성한 결과입니다. hunt.io 리포트에 따르면 ApkBuilder.php가 com.icontrol.protector라는 원본 패키지명을 cloud, manager, core 등의 정상적으로 보이는 단어들로 교체하며, 클래스명 역시 8~14자의 랜덤 문자열로 치환합니다.

분석 범위

이 글은 배포 경로나 인프라가 아닌 빌드된 APK 자체의 코드 분석에 집중합니다. 분석 도구로는 jadx를 사용했으며, 에셋 복호화를 위한 Python 스크립트를 별도로 작성했습니다.

APK 내부에서는 다수의 클래스명과 메서드명이 의미를 알기 어려운 이름으로 바뀌어 있었습니다. 비교에 사용되는 문자열도 암호화되어 있어 코드의 역할을 바로 파악하기 어려웠습니다. 여기에 더해 에셋 파일(.bt)에는 XOR 암호화가, C2 설정값에는 AES-128-CBC 암호화가 적용되어 있어 디컴파일만으로는 핵심 데이터를 확인할 수 없었습니다. 이를 해제하기 위해 키를 추출하고 별도의 복호화 도구를 작성했습니다.

이제 이 APK가 사용자를 속이기 위해 어떤 장치들을 숨겨두었는지 코드를 따라가 보겠습니다.

OEM 판별과 사회공학 기법

제조사 판별 로직

분석 대상 앱에는 기기의 브랜드를 확인하고 그에 맞는 동작을 선택하는 코드가 있습니다. 난독화된 클래스 ev의 메서드 a()가 그 역할을 하는데, jadx로 디컴파일한 결과를 보면 비교 대상 문자열이 전부 v90.a() 호출로 감싸져 있어 어떤 브랜드를 판별하는지 바로 알 수 없습니다.

[그림 1] ev.a() 디컴파일 결과

[그림 1] ev.a() 디컴파일 결과

v90.a()를 따라가면 내부적으로 w90.a()에 위임하고, w90의 실제 구현은 repeating-key XOR입니다. 첫 번째 byte 배열이 암호문, 두 번째가 키이며, 키를 순환하면서 XOR하여 UTF-8 문자열을 반환합니다.

[그림 2]  w90 클래스 - 런타임 문자열 복호화 로직

[그림 2] w90 클래스 - 런타임 문자열 복호화 로직

이 로직을 Python으로 재현하면 ev.a() 안의 모든 암호화된 문자열을 복호화할 수 있습니다.

# v90/w90 문자열 복호화 — repeating-key XOR
def decrypt_v90(data, key):
    return bytes(d ^ key[i % len(key)] for i, d in enumerate(data)).decode('utf-8')

# 예시: return 1에 해당하는 문자열
decrypt_v90(bytes([172,191,41,226,97,225]), bytes([196,202,72,149,4,136,121,236]))
# → "huawei"

전체 복호화 결과는 다음과 같습니다.

반환값 복호화된 브랜드 비고
1 huawei
2 xiaomi, redmi, mi Xiaomi 계열 통합
3 oppo, realme, oneplus OPPO 계열 통합
4 vivo
5 meizu
6 samsung
7 letv
8 smartisan
10 lenovo
11 zte
12 coolpad, 360
13 gionee
14 honor, hihonor Honor 계열
15 google
0 (미식별) 기본값

[표 2] v90.a() 복호화 결과 - ev.a() 브랜드 매핑

복호화된 코드를 정리하면 다음과 같습니다.

// ev.a() — v90 복호화 후 정리
public static int a() {
    String brand = Build.BRAND.trim().toLowerCase();
    if (brand.equals("huawei"))                          return 1;
    if (brand.equals("xiaomi") || brand.equals("redmi")
        || brand.equals("mi"))                           return 2;
    if (brand.equals("oppo") || brand.equals("realme")
        || brand.equals("oneplus"))                      return 3;
    if (brand.equals("vivo"))                            return 4;
    if (brand.equals("meizu"))                           return 5;
    if (brand.equals("samsung"))                         return 6;
    if (brand.equals("letv"))                            return 7;
    if (brand.equals("smartisan"))                       return 8;
    if (brand.equals("lenovo"))                          return 10;
    if (brand.equals("zte"))                             return 11;
    if (brand.equals("coolpad") || brand.equals("360")) return 12;
    if (brand.equals("gionee"))                          return 13;
    if (brand.equals("honor") || brand.equals("hihonor")) return 14;
    if (brand.equals("google"))                          return 15;
    return 0;
}

이 함수는 20개의 브랜드 문자열과 별칭을 14개 그룹으로 분류하며, 일치하는 값이 없으면 0을 반환합니다. 예를 들어 redmimi를 Xiaomi와 같은 반환값으로 처리하지만, 코드 안에 모회사 관계를 판정하는 별도 로직이 있는 것은 아닙니다. 가짜 업데이트 화면(.bt 파일)이 준비되어 있는 것은 Huawei, Xiaomi, OPPO, vivo, Samsung, Honor 계열, 그리고 범용 Android까지 7종입니다. 화면 녹화 권한 처리에서는 이 중 일부 OEM과 Google에 대해 별도의 UI 자동화 분기가 존재하고, 나머지는 공통 승인 버튼 처리로 이어집니다.

이 반환값은 이후 두 가지 목적으로 사용됩니다. 하나는 이 장에서 다루는 제조사별 가짜 업데이트 화면 선택이고, 다른 하나는 다음 장에서 다룰 화면 녹화 권한 자동 탈취 시 UI 자동화입니다. 같은 판별 함수 하나로 사회공학 은폐와 권한 탈취를 동시에 제어하는 구조입니다.

암호화된 에셋 파일

APK의 assets/ 디렉토리에는 0.bt부터 10.bt까지 11개의 파일이 존재합니다. 확장자만 보면 정체를 알 수 없지만, 빌더 설정 클래스(ydctivwk)에서 15자리 XOR 키를 추출하여 복호화하면 모두 HTML 파일임이 드러납니다.

# XOR 복호화 — 키: ydctivwk.wuvazisnxh
ASSET_KEY= b"eovypcszcgnwjj1"

def decrypt_asset(data, key=ASSET_KEY):
    return bytes(b^ key[i% len(key)] for i, b in enumerate(data))

복호화 결과, 11개 파일은 크게 두 종류로 나뉩니다.

파일 유형 용도
0.bt 잠금화면 — PIN 입력 (중국어) 사용자 PIN 탈취
1.bt 잠금화면 — 패턴 잠금 사용자 패턴 탈취
2.bt 잠금화면 — PIN 입력 사용자 PIN 탈취
3.bt 잠금화면 — 비밀번호 입력 사용자 비밀번호 탈취
4.bt 시스템 업데이트 — 범용 블랙 스크린 (별도 화면이 없는 브랜드 및 미식별 기기)
5.bt 시스템 업데이트 — Huawei 블랙 스크린
6.bt 시스템 업데이트 — HarmonyOS 블랙 스크린
7.bt 시스템 업데이트 — vivo 블랙 스크린
8.bt 시스템 업데이트 — OPPO 블랙 스크린
9.bt 시스템 업데이트 — Xiaomi 블랙 스크린
10.bt 시스템 업데이트 — Samsung 블랙 스크린

[표 3] 복호화된 에셋 파일 구성

잠금화면 파일(0~3.bt)은 사용자의 기기 잠금 비밀번호(패턴)를 직접 탈취하기 위한 피싱 페이지이고, 시스템 업데이트 파일(4~10.bt)은 사용자의 시선을 묶어두는 은폐용 화면입니다.

제조사별 가짜 업데이트 화면

ev.a()의 반환값에 따라 어떤 .bt 파일을 로드할지가 결정됩니다.

ev.a() 제조사 에셋 파일 로고
1 Huawei 5.bt Huawei 로고
2 Xiaomi 9.bt Xiaomi 로고
3 OPPO 8.bt OPPO 로고
4 vivo 7.bt vivo 로고
6 Samsung 10.bt Samsung 로고
14 Honor / HiHonor 6.bt HarmonyOS 로고
그 외 반환값 별도 화면이 없는 브랜드 또는 미식별 4.bt 안드로이드 로고

[표 4] ev.a() 반환값과 에셋 파일 매핑

[그림 3] 복호화된 가짜 업데이트 화면 - Samsung, Huawei, Xiaomi, OPPO, vivo, HarmonyOS

[그림 3] 복호화된 가짜 업데이트 화면 - Samsung, Huawei, Xiaomi, OPPO, vivo, HarmonyOS

복호화된 HTML 파일들을 열어보면, 제조사별 시스템 업데이트 화면을 모방한 구성을 확인할 수 있습니다. 로고는 Base64로 인코딩되어 HTML 안에 내장되어 있고, 진행률 바와 업데이트 안내 문구가 함께 배치되어 있습니다. 안내 문구는 기기의 언어 설정에 맞게 표시됩니다.

이 가짜 화면은 사용자가 보고 있던 화면 위를 덮어 정상적인 시스템 업데이트가 진행 중인 것처럼 보이게 합니다. 사용자가 업데이트가 끝나기를 기다리도록 유도하면서, 그 뒤에서 이루어지는 조작을 알아차리기 어렵게 만드는 것입니다.

블랙 스크린의 목적

그렇다면 이 가짜 업데이트 화면은 왜 필요할까요?

정상적인 업데이트처럼 보이는 화면은 사용자가 기기를 조작하지 않고 기다리도록 유도합니다. 공격자는 이를 이용해 화면 녹화 승인이나 원격 조작 과정에서 나타나는 변화를 사용자가 알아차리기 어렵게 만들 수 있습니다.

다만 가짜 화면 표시와 화면 녹화 시작은 각각의 명령과 상태에 따라 이루어집니다. 항상 함께 실행되는 것은 아니지만 가짜 업데이트 화면을 다른 악성 행위를 숨기는 데 활용할 수 있는 구조입니다.

잠금화면 자격증명 탈취 오버레이

시스템 업데이트 화면(4~10.bt)이 사용자의 시선을 묶는 은폐용이라면, 잠금화면 파일(0~3.bt)은 자격증명을 직접 수집하는 피싱 페이지입니다.

[그림 4] 복호화된 잠금화면 오버레이 - PIN(중국어), 패턴, PIN, 비밀번호

[그림 4] 복호화된 잠금화면 오버레이 - PIN(중국어), 패턴, PIN, 비밀번호

복호화된 4개의 HTML 파일은 각각 다른 유형의 잠금 해제 방식을 모방합니다.

파일 유형 입력 방식 특징
0.bt PIN 입력 (중국어) 6자리 숫자 넘버패드 밝은 배경, “验证锁屏密码”, 4/6자리·혼합·패턴 전환 가능
1.bt 패턴 잠금 3×3 도트 드래그 어두운 배경, [TITLE]/[DIS] 플레이스홀더
2.bt PIN 입력 가변 길이 숫자 어두운 배경, AOSP 스타일 넘버패드
3.bt 비밀번호 입력 텍스트 필드 어두운 배경, “Enter password”

[표 5] 잠금화면 오버레이 구성

이 오버레이들은 공통적으로 JavaScript의 CallBacker.OK() 메서드를 통해 입력값을 네이티브 코드로 전달합니다. 다만 파일마다 입력 방식과 제출 시점은 다릅니다.

// 0.bt — 자격증명 전달 (PIN 유형)
function submitResult(password) {
    const pinstr= currentType+ '|' + firstPassword+ '|' + password;
    CallBacker.OK(pinstr);  // WebView → 네이티브 Java 콜백
}

// 2.bt — 자격증명 전달 (PIN 유형)
function ClickOK(pinstr) {
    CallBacker.OK(pinstr);  // 첫 번째 입력 + "---" + 두 번째 입력
}

일부 잠금화면은 의도적으로 오류를 표시해 재입력을 유도합니다. 예를 들어 0.bt는 첫 입력값을 저장한 뒤 “비밀번호 오류, 다시 입력하세요”라는 메시지를 보여줍니다. 사용자가 다시 입력하면 두 값을 묶어 앱 내부의 처리 코드로 전달합니다. 실제 기기 비밀번호를 검증한 것이 아니라 사용자가 오타를 의심하고 다시 입력하도록 유도하는 것입니다.

2.bt에도 두 번의 입력값을 묶어 전달하는 처리가 있습니다. 반면 3.bt는 한 번 제출한 값을 바로 전달합니다. 따라서 모든 잠금화면이 같은 방식으로 입력값을 수집하는 것은 아닙니다.

0.bt는 중국어 UI를 사용하며 한 화면에서 4자리·6자리 PIN, 혼합 비밀번호, 패턴 잠금을 선택할 수 있습니다. 1~3.bt에 표시되는 오류 문구 일부는 기기 언어에 따라 바뀌며 제목과 안내 문구에는 앱에 저장된 설정값이 반영됩니다.

피해자 단말 화면 실시간 모니터링

MediaProjection 권한 요청

화면 녹화 권한 요청을 담당하는 것은 pjndpxduo 클래스입니다. 이 클래스는 별도의 콘텐츠 화면 없이 시스템 동의 화면을 열고 화면 캡처 서비스의 시작·중지를 처리합니다.

C2 서버에서 화면 녹화 명령(SN)이 내려오면, g.h()COM=ON 인텐트를 구성하고 pjndpxduo를 실행합니다. pjndpxduo는 다음과 같이 MediaProjection 권한 획득을 시도합니다.

[그림 5] pjndpxduo.f() MediaProjection 권한 요청

[그림 5] pjndpxduo.f() MediaProjection 권한 요청

// pjndpxduo.f() — 문자열 복호화 후 정리
private void f() {
    MediaProjectionManager mpm =
        (MediaProjectionManager) getSystemService("media_projection");

    startActivityForResult(mpm.createScreenCaptureIntent(), 100);
}

createScreenCaptureIntent()는 화면 녹화 동의를 요청하기 위한 정보를 만들고, 이어지는 startActivityForResult()가 시스템 동의 화면을 엽니다. 정상적인 앱에서는 사용자가 내용을 확인한 뒤 허용 여부를 선택합니다. 그러나 분석 대상 앱은 사용자가 직접 선택하기 전에 접근성 서비스를 이용해 승인 버튼을 누르려고 합니다.

AccessibilityService를 통한 자동 승인

f()가 시스템 동의 화면을 열면 onCreate()는 800밀리초를 기다린 뒤 접근성 서비스의 getRootInActiveWindow()로 현재 화면의 버튼과 입력창 정보를 가져옵니다. 이어서 b()가 제조사에 맞게 화면 공유 방식 선택을 처리하고, c()가 허용 버튼을 찾아 자동 클릭을 시도합니다.

// pjndpxduo.onCreate() — 다이얼로그 띄운 뒤 자동 승인
f();  // 시스템 동의 다이얼로그 표시
Thread.sleep(800);  // 다이얼로그 렌더링 대기

AccessibilityNodeInfo root = d().getRootInActiveWindow();
if (root != null) {
    b(root);  // OEM별 자동 클릭 실행
}

OEM별 UI 자동화

앞서 살펴본 제조사 판별 함수 ev.a()는 화면 녹화 권한을 얻는 과정에서도 사용됩니다. 제조사와 Android 버전에 따라 화면 공유 방식이나 허용 버튼의 구성이 다르기 때문입니다.

이 앱은 이러한 차이에 맞춰 조작 방법을 나눠 놓았습니다. Xiaomi 기기는 Android 14 이상에서 별도 처리를 수행하며 OPPO, vivo, Samsung, Honor, Google 기기는 Android 15 이상에서 각각의 처리 경로를 사용합니다. 공유 방식 선택 메뉴를 열고 특정 항목을 누른 뒤 승인 버튼을 찾는 방식입니다.

[그림 6] 제조사와 Android 버전에 따른 화면 공유 선택 메뉴 조작

[그림 6] 제조사와 Android 버전에 따른 화면 공유 선택 메뉴 조작

버튼을 찾을 때는 화면에 표시된 글자 대신 각 요소에 부여된 내부 식별자를 사용합니다. 쉽게 말해 “허용”이라는 글자를 눈으로 찾는 것이 아니라 해당 버튼을 코드에서 직접 찾아 누르는 것입니다. 문자열을 복호화하면 다음과 같은 식별자를 확인할 수 있습니다.

화면 요소 복호화된 식별자 코드에서의 용도
공유 방식 선택 메뉴 com.android.systemui:id/screen_share_mode_spinner 선택 메뉴 열기
메뉴 항목 android:id/text1 공유 방식 선택 항목 탐색
vivo 메뉴 항목 com.android.systemui:id/menu_content vivo 분기에서 선택 항목 탐색
Honor 선택 항목 com.android.systemui:id/hwradio Honor 분기에서 선택 항목 탐색
시스템 확인 버튼 android:id/button1 승인 버튼 클릭 시도
사용 중 허용 버튼 com.android.permissioncontroller:id/permission_allow_foreground_only_button 권한 허용 버튼 클릭 시도
허용 버튼 com.android.permissioncontroller:id/permission_allow_button 권한 허용 버튼 클릭 시도

[표 6] 자동 클릭에 사용되는 화면 요소 식별자

공유 방식 선택을 처리한 뒤에는 c() 메서드가 세 종류의 승인 버튼을 차례로 검색합니다. 일치하는 버튼을 찾으면 접근성 기능으로 클릭을 요청합니다. 여러 식별자를 준비해 둔 것은 기기마다 다른 권한 요청 화면에 대응하려는 것으로 볼 수 있습니다.

다만 이 과정이 성공하려면 접근성 서비스가 활성화되어 있고 대상 버튼도 검색 가능한 상태여야 합니다. 앱이 예상한 화면 구성과 실제 화면이 다르면 자동 승인이 실패할 수 있습니다.

Android 버전별 대응

화면 녹화를 다시 시작하는 방식도 Android 버전에 따라 나뉩니다. 아래 조건은 이전 승인 결과를 재사용하는 경로입니다.

// pjndpxduo.onCreate() 일부
if (tc.l != null && tc.m != -999 && Build.VERSION.SDK_INT < 34) {
    startService(syulybnwnzql.s(this, tc.m, tc.l, b));
    finish();
    return;
}

Android 14 미만에서는 저장된 승인 결과가 있으면 이를 이용해 화면 캡처 서비스를 다시 시작하려고 합니다. 이 조건에 해당하지 않으면 새 동의 화면을 띄웁니다. Android 14 이상도 이 경로를 거치며 접근성 서비스를 통한 자동 클릭을 시도합니다.

공격자는 이처럼 기존 승인 결과를 재사용하는 경로와 다시 승인을 요청하는 경로를 모두 준비해 두었습니다.

공격 흐름 요약

화면 녹화 명령을 받은 이후의 흐름은 다음과 같습니다.

  1. C2 서버에서 화면 녹화 명령을 받습니다.
  2. 이전 승인 결과를 재사용할 수 있는지 확인합니다.
  3. 새 승인이 필요하면 동의 화면을 띄우고 약 800밀리초 뒤 화면 요소를 찾습니다.
  4. 제조사에 맞는 처리 경로로 공유 방식 선택과 허용 버튼 클릭을 시도합니다.
  5. 승인이 완료되면 화면을 연속으로 캡처해 C2 서버로 전송합니다.

앞서 살펴본 가짜 업데이트 화면은 이러한 조작을 숨기는 데 활용될 수 있습니다. 사용자가 업데이트가 끝나기를 기다리도록 유도하는 동안 화면에서는 권한 요청과 원격 조작이 진행될 수 있는 것입니다. 다만 가짜 화면 표시와 녹화 시작은 각각의 명령과 상태에 따라 이루어지므로 항상 함께 실행되는 것은 아닙니다.

C2 통신과 원격 제어

공격자의 명령을 기다리는 연결

화면을 가리고 녹화하는 기능은 공격자가 필요할 때 실행할 수 있습니다. 이를 위해 앱은 공격자의 명령 서버인 C2와 연결을 유지합니다. 이 연결을 담당하는 클래스가 mkblgolr입니다.

분석 대상 앱에서 확인한 연결 주소는 다음과 같습니다.

wss://fusu666[.]cc/api/ws/?dk=...

통신에는 웹소켓(WebSocket)을 사용합니다. 연결을 열어 두고 양쪽에서 메시지를 주고받을 수 있어 공격자가 명령을 보내거나 앱이 수집한 정보를 전달하는 데 사용됩니다. 주소의 wss는 TLS로 보호되는 웹소켓 연결을 뜻합니다.

앱에 저장된 일부 설정에는 별도의 암호화도 적용되어 있습니다. 앞서 .bt 파일에 사용된 XOR과 달리 여기서는 AES-128-CBC를 사용합니다. ov 클래스를 따라가면 설정 클래스에 저장된 값으로 복호화 키를 만드는 과정을 확인할 수 있습니다. 이 과정을 Python으로 재현해 암호화된 설정값을 확인했습니다.

즉, APK 내부의 설정을 숨기는 암호화와 네트워크 통신을 보호하는 암호화가 각각 사용되는 구조입니다.

명령을 받아 기능을 실행하는 방식

서버와 앱은 JSON 형식으로 메시지를 주고받습니다. 앱은 수신한 메시지의 type으로 처리할 기능을 고르고 subc로 세부 동작을 구분합니다. 공격자가 명령을 보내면 해당 기능을 담당하는 메서드로 전달되는 방식입니다.

[그림 7] 수신한 명령을 기능별 처리 메서드로 전달하는 코드

[그림 7] 수신한 명령을 기능별 처리 메서드로 전달하는 코드

위 코드에서 호출하는 메서드들은 각각 화면 조작이나 정보 수집 등의 기능을 담당합니다. 주요 명령과 처리 기능을 정리하면 다음과 같습니다.

명령 유형 처리 메서드 주요 기능
screen g.h() 화면 녹화 시작·중지, 화면 캡처, 터치·입력 조작
screencomd g.i() 카메라, 문자, 연락처, 앱 실행, 파일 및 원격 잠금 제어
loc g.f() 위치 정보 관련 제어
mic g.g() 마이크 관련 제어
fetch, file g.d(), g.e() 파일 관련 요청 처리

[표 7] 주요 C2 명령과 처리 기능

앞 장의 화면 녹화도 이 구조로 시작됩니다. 서버에서 screen 유형의 SN 명령을 보내면 g.h()가 화면 녹화 권한을 요청하는 pjndpxduo를 실행합니다. 승인이 완료되면 화면 수집과 전송이 이어집니다.

이처럼 공격자는 직접 기기를 만지지 않고도 연결된 앱을 통해 여러 기능을 선택적으로 실행할 수 있습니다.

기기 정보와 파일 탈취

앱은 연결 과정에서 기기 식별자와 모델명, Android 버전, 배터리 상태, 네트워크 정보 등을 수집해 서버에 전달합니다. 접근성 서비스의 활성화 여부도 포함되어 있어 공격자가 기기의 상태와 조작 가능 여부를 파악하는 데 사용할 수 있습니다.

파일을 가져오는 기능도 있습니다. 요청받은 파일을 읽어 여러 조각으로 나눈 뒤 파일명과 전체 크기, 전송한 크기를 함께 보냅니다. 각 조각은 Base64 문자열로 바꿔 JSON 메시지에 담습니다. 여기서 Base64는 파일 데이터를 메시지에 넣기 위한 표현 방식이며 암호화는 아닙니다.

문자와 연락처를 조회하거나 문자를 발송하는 명령도 확인됩니다. 다만 실제로 접근할 수 있는 정보는 앱에 허용된 권한과 Android의 저장소 제한에 따라 달라집니다. 이러한 기능이 있다고 해서 다른 앱의 내부 데이터를 자유롭게 읽을 수 있는 것은 아닙니다.

원격 잠금 명령

원격 제어 명령 중에는 잠금화면을 표시하는 DIAO도 있습니다. 이 명령을 받으면 앱은 서버가 보낸 잠금값과 제목, 안내 문구, 화면 유형을 저장하고 잠금 기능을 활성화합니다.

여기에는 앞서 살펴본 잠금화면 HTML이 사용됩니다. 공격자는 표시할 문구와 입력 방식을 바꿀 수 있으며 사용자가 입력한 값은 앱의 처리 코드로 전달됩니다.

잠금을 해제하는 조건도 존재합니다. 저장된 잠금값이 없으면 해제하고 값이 있으면 사용자 입력과 비교합니다. 따라서 이 기능은 원격 잠금이나 잠금정보 수집에 활용될 수 있습니다. 다만 현재 확인한 코드만으로 금전을 요구하는 랜섬웨어라고 단정하기는 어렵습니다.

연결과 동작을 유지하려는 시도

공격자가 계속해서 기기를 제어하려면 앱이 백그라운드에서도 동작해야 합니다. mkblgolr에는 화면이 꺼져도 작업을 이어가도록 기기 절전 모드 진입을 제한하는 코드가 포함되어 있습니다.

서비스가 종료되거나 최근 앱 목록에서 작업이 제거될 때 자신을 다시 시작하려는 코드도 확인됩니다. Android의 실행 제한에 따라 재시작이 막힐 수 있지만 연결과 동작을 유지하려는 의도는 분명합니다.

이 연결을 바탕으로 공격자는 기기 상태를 확인하고 화면 수집이나 파일 탈취를 반복해서 요청할 수 있습니다. 다음 장에서는 이 원격 제어 기능에 더해 암호화폐 앱을 직접 겨냥하는 코드가 어떻게 동작하는지 살펴보겠습니다.

암호화폐 거래소·지갑 자격증명 탈취

암호화폐 앱을 겨냥한 입력 감시

이 앱에는 화면을 보고 원격으로 조작하는 기능 외에도 암호화폐 앱을 직접 겨냥하는 코드가 포함되어 있습니다. 이를 담당하는 y 클래스는 사용자가 특정 입력창을 선택하거나 값을 입력하는 순간을 감시합니다.

대상은 Binance, Gate.io, TokenPocket, imToken, Bitget입니다. 각 앱의 입력창과 버튼에 부여된 내부 식별자를 코드에 미리 넣어 두고 해당 화면이 나타나면 수집이나 후속 조작을 시작합니다.

대상 앱 감시 대상과 주요 동작
Binance 특정 입력창을 선택하면 거래 화면 조작 기능 호출
Gate.io 펀드 비밀번호 입력 감시 및 추가 인증 화면에서 후속 기능 호출
TokenPocket 지갑 비밀번호 입력값 수집·저장
imToken 거래 비밀번호 입력값 수집·저장
Bitget 숫자 입력값 수집 및 추가 인증 화면에서 후속 기능 호출

[표 8] 암호화폐 앱별 감시 대상과 주요 동작

다만 이 기능이 설치 직후 무조건 실행되는 것은 아닙니다. 공통 설정인 onc_btry와 앱별 활성화 조건이 충족되어야 동작합니다. 조건이 켜진 이후에는 대상 화면에서 발생하는 이벤트에 반응하므로 입력할 때마다 별도의 C2 명령을 받을 필요는 없습니다.

입력창을 찾아 값을 모으는 방식

TokenPocket 처리 부분을 보면 수집 과정을 쉽게 확인할 수 있습니다. 앱은 먼저 edt_dialog_pwd라는 비밀번호 입력창이 선택되었는지 확인합니다. 해당 입력창을 찾으면 수집을 시작하고 이후 전달되는 입력 관련 텍스트를 이어 붙입니다. 마지막으로 정해진 화면 전환 조건이 충족되면 모은 값을 paytp라는 이름으로 저장합니다.

[그림 8] TokenPocket 비밀번호 입력창을 감시하고 수집한 값을 저장하는 코드

[그림 8] TokenPocket 비밀번호 입력창을 감시하고 수집한 값을 저장하는 코드

이 과정에서는 중국어 문자와 비밀번호를 가리는 점 기호를 제거합니다. 다만 점 기호를 지운다고 숨겨진 비밀번호가 복원되는 것은 아닙니다. 실제로 확보할 수 있는 값은 대상 앱이 접근성 기능을 통해 어떤 텍스트를 노출하는지에 따라 달라집니다.

Gate.io와 imToken에도 비슷한 수집 흐름이 있습니다. Bitget에서는 버튼 클릭으로 전달되는 텍스트 중 숫자만 모으는 처리가 확인됩니다. 공격자는 앱마다 다른 입력 방식에 맞춰 수집 코드를 따로 준비한 것입니다.

수집 이후의 거래 화면 조작

후속 코드를 따라가면 입력값 수집에서 끝나지 않는다는 점도 드러납니다. Binance 처리부인 v6에는 거래 확인 화면을 모방해 표시하는 기능이 있습니다. 화면에서 읽은 정보로 금액과 수수료를 구성하고 실제 앱의 입력값을 바꾸거나 버튼을 누르는 코드도 포함되어 있습니다.

Gate.io 처리부인 f는 인증 코드와 비밀번호를 읽어 둔 뒤 출금 주소를 바꾸고 다음 단계로 진행하도록 구성되어 있습니다. 이후 확보한 인증값을 다시 입력하고 확인 버튼을 누릅니다. Bitget 처리부인 me에서도 주소 입력과 인증값 재입력, 송금 관련 버튼 조작이 확인됩니다.

이때 입력할 주소에는 C2 명령으로 설정할 수 있는 ydctivwk.usdtadress 값이 사용됩니다. 공격자가 지정한 주소를 거래 화면에 넣고 수집한 정보를 후속 인증에 재사용하려는 흐름입니다.

따라서 이 앱은 자격증명을 모으는 기능과 거래 화면을 자동으로 조작하는 기능을 함께 갖추고 있습니다. 다만 이번 정적 분석에서 확인한 것은 주소와 인증값을 입력하고 확인 버튼을 누르는 코드까지입니다. 실제 출금 승인이나 자금 이동이 완료되는지는 별도의 실행 검증이 필요합니다.

AccessibilityService

여러 악성 기능을 연결하는 접근성 서비스

지금까지 살펴본 화면 가리기와 자동 클릭, 입력값 수집에는 공통점이 있습니다. 모두 Android의 접근성 서비스인 AccessibilityService를 활용한다는 점입니다. 분석 대상 앱에서는 mrryylvfy 클래스가 이 역할을 담당합니다.

접근성 서비스는 원래 화면을 읽어 주거나 터치 조작을 보조하는 기능입니다. 이를 위해 화면에 표시된 요소를 확인하고 버튼을 누르거나 입력 변화를 감지할 수 있습니다. 이 앱은 같은 기능을 사용해 권한 요청 화면을 조작하고 암호화폐 앱의 입력값을 수집합니다.

사용자가 보는 화면 위에 가짜 업데이트 화면이나 잠금화면을 표시하는 데도 접근성 서비스가 쓰입니다. 화면을 가리는 기능과 그 뒤에서 이루어지는 조작이 같은 권한을 기반으로 구현된 것입니다.

화면 읽기에서 원격 조작까지

mrryylvfy는 앱 전환이나 입력 같은 변화가 발생하면 관련 정보를 확인하고 각 처리부로 전달합니다. 앞서 살펴본 y 클래스도 이렇게 전달된 정보를 바탕으로 암호화폐 앱의 입력창을 감시합니다.

화면 녹화 권한을 얻는 과정에서도 접근성 서비스가 사용됩니다. pjndpxduo가 시스템 동의 화면을 띄우면 화면 요소를 찾아 허용 버튼을 누르도록 요청합니다. 별도로 접근성 서비스 자체의 스크린샷 기능을 호출해 화면을 수집하는 코드도 존재합니다.

접근성 서비스 자체가 취약점인 것은 아닙니다. 사용자가 앱의 설명을 믿고 권한을 허용하면 정상적인 보조 기능이 악성 앱의 감시와 조작에 재사용될 수 있다는 것이 문제입니다.

이처럼 mrryylvfy는 화면 감시와 입력 수집, 자동 조작을 연결합니다. 앞서 살펴본 악성 기능들이 동작하는 데 접근성 서비스가 핵심 기반으로 사용되는 것입니다.

결론

이번 글에서는 Flying Eagle 프레임워크로 만들어진 APK를 분석해 사용자를 속이는 화면과 그 뒤의 원격 제어 기능을 살펴봤습니다.

앱은 기기의 브랜드에 맞는 가짜 업데이트 화면을 준비하고 접근성 서비스를 이용해 화면 녹화 승인을 시도합니다. C2를 통해 화면과 파일을 전송하는 기능도 갖추고 있습니다. 여기에 암호화폐 앱의 입력값을 수집하고 출금 주소와 인증값을 입력하는 후속 조작까지 결합되어 있었습니다. 다만 실제 출금이 완료되는지는 이번 정적 분석의 확인 범위에 포함되지 않습니다.

이 위협은 분석한 APK 하나로 끝나지 않습니다. 소스코드와 빌더가 유출된 프레임워크는 위장 화면과 패키지명, 서버 주소를 바꾼 변종으로 재사용될 수 있습니다. 공격자가 기존 기능을 조합하거나 수집 대상을 추가하면 겉모습은 달라도 유사한 동작을 수행하는 악성 앱이 만들어질 수 있는 것입니다.

따라서 특정 앱 이름이나 도메인만으로 위협을 판단하기는 어렵습니다. 접근성 권한을 어떤 목적으로 사용하는지, 다른 앱의 입력창을 감시하는지, 수집한 정보를 외부로 보내는지처럼 실제 동작을 함께 살펴봐야 합니다.

이처럼 이름과 구성을 바꾼 악성 앱이 반복해서 등장하는 환경에서는 개별 앱에 대한 심층 분석과 함께 의심스러운 앱을 신속하게 판별하는 대응 체계도 중요합니다. 기업과 기관에서는 악성 앱 자동분석 솔루션을 활용해 이러한 분석 업무를 지원할 수 있습니다. 이번 분석 대상 APK 역시 자사의 AI 기반 악성 앱 자동분석 솔루션인 온앱스캔(OnAppScan)에서 탐지되는 것을 확인했습니다

[그림 9] 온앱스캔의 분석 대상 앱 탐지 결과

[그림 9] 온앱스캔의 분석 대상 앱 탐지 결과

이러한 탐지·분석 체계와 함께 사용자의 주의도 필요합니다. 출처가 불분명한 APK는 설치하지 않고 앱의 기능과 무관한 접근성 권한 요청은 허용하지 않는 것이 중요합니다. 특히 업데이트나 보안 검사를 이유로 화면을 읽거나 조작할 권한을 요구한다면 요청의 목적을 다시 확인해야 합니다.

Flying Eagle은 정상적인 Android 기능이 사용자를 속이는 화면과 원격 명령 체계에 결합되면 공격 수단으로 악용될 수 있음을 보여줍니다. 익숙한 제조사 로고나 그럴듯한 안내 문구만으로 앱을 신뢰해서는 안 됩니다. 겉으로 보이는 모습보다 실제 동작과 권한 사용을 살펴봐야 하는 이유입니다.

You May Also Like

안드로이드 악성코드 ALBIRIOX 심층 분석
안드로이드 악성코드 ALBIRIOX 심층 분석

안녕하세요. 분석팀의 Jiyong입니다. 모바일 악성코드가 단순한 정보 유출을 넘어 사용자의 가상화폐 지갑을 직접 노리는 형태로 고도화되고 있습 …

안녕하세요. 분석팀의 Jiyong입니다. 모바일 악성코드가 단순한 정보 유출을 넘어 사용자의 가상화폐 지갑을 직접 노리는 형태로 고도화되고 있습니다. 오늘은 보안 업계에서 이슈가 됐었던 악성코드를 직접 입수하여, 껍데기인 드로퍼부터 악성 행위의 심장부까지 코드 레벨에서 파헤쳐본 분석 결과를 공유하려 합니다.

안드로이드 악성코드의 진화: 스파이웨어(Spyware)
안드로이드 악성코드의 진화: 스파이웨어(Spyware)

안녕하세요. Secolt입니다. 어느덧 2025년도 막바지를 향해 달려가고 있습니다. 갑작스럽게 추워진 날씨에 다들 건강 관리는 잘 하고 계신가 …

안녕하세요. Secolt입니다. 어느덧 2025년도 막바지를 향해 달려가고 있습니다. 갑작스럽게 추워진 날씨에 다들 건강 관리는 잘 하고 계신가요? 연말이 다가오면서 거리의 분위기는 들뜨고 있지만, 악성코드 분석가들의 마음은 마냥 편하지는 않습니다. 연말연시는 해커들이 가장 활발하게 움직이는 시기이기도 하니까요.