언어: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
neverd mobile은 NeverD 내장 엔진만 사용해 APK, DEX, smali에서 읽기 쉬운 Java를 복원합니다. 독립적으로 구현한 판독기가 타입 정보를 갖춘 Dalvik 모델과 처리량이 제한된 Java 생성기를 공유합니다. 실험적인 CLI 기능이며 임의 APK의 완전한 복원을 보장하지 않습니다. 네이티브 C SDK, Python 플러그인 SDK, GUI 로더, neverd decompile --language는 APK 컨테이너와 Java 출력의 진입점을 제공하지 않습니다.
복구된 Java는 바이트코드를 재구성한 결과입니다. 원래 주석, 서식, 원본 소스 언어의 선택, 제거된 식별자는 되살릴 수 없습니다. Kotlin 바이트코드도 Java로 출력됩니다. 실행 성공이 의미적 동등성을 증명하거나 모든 메서드의 재컴파일을 보장하지는 않습니다. 이 과정에서 분석 대상 애플리케이션을 실행하지 않습니다.
NeverD를 빌드한 뒤 새 출력 디렉터리를 지정하세요.
neverd mobile app.apk -o recovered-app
neverd mobile classes.dex -o recovered-dex
neverd mobile MainActivity.smali -o recovered-class
neverd mobile decoded/smali -o recovered-javarecovered-app/sources/에서 Java 파일을 읽고, recovered-app/report.json에서 입력 목록과 제한 사항을 확인할 수 있습니다. smali 클래스가 서로 참조한다면 디렉터리를 입력으로 사용하는 것이 좋습니다.
| 구성 요소 | 요구 사항 | 선택 순서 |
|---|---|---|
| NeverD | C++20을 지원하는 도구 체인으로 neverd 대상을 빌드합니다. 모바일 작업 흐름은 네이티브 CLI에 포함되며 Python 인터프리터를 호출하지 않습니다. 배포할 때 해당 빌드에 필요한 네이티브 라이브러리를 함께 제공하세요. |
build/bin/neverd / PATH |
Android 복원은 NeverD의 C++20 내장 엔진만 사용하며 Python 또는 Java 런타임이 필요하지 않습니다. DEX 035, 037–040 및 smali에서 표현할 수 있는 일반 선언과 연산을 처리합니다. DEX 041, invoke-custom 같은 동적 호출, 일부 초기화 경로, 알 수 없는 의미적 어노테이션이나 연산, Java로 표현할 수 없는 식별자는 명시적으로 실패합니다. 파일 형식을 받는다고 그 형식의 모든 명령과 선언을 지원하는 것은 아닙니다.
Java 이름 바인딩은 클래스 헤더와 본문을 구분하고 확인된 같은 패키지 내 이름 가림을 처리하지만, 외부 상위 클래스나 인터페이스 선언이 부족해 타입 이름 또는 생성된 Java 보조 코드의 참조 대상을 확정할 수 없으면 명시적으로 거부하며, 선언 없이도 상속 가능한 멤버 타입이 없다고 가정하는 외부 타입은 현재 java.lang.Object뿐입니다. Smali 부동소수점 리터럴은 대상 단정밀도 또는 배정밀도로 직접 반올림하여 그 결과의 비트 패턴을 보존합니다.
내장 C++ 엔진은 지원 범위에 있는 클래스·필드·메서드의 Signature 메타데이터를 보존하고 검증합니다. 타입 변수, 배열, 와일드카드, 타입 경계, 메서드 수준의 이름 가림을 다루며, 타입 소거 결과는 원본 DEX 선언의 식별 정보와 일치해야 합니다. Throws는 버리지 않으며 예외 상속 관계를 입증할 수 없으면 거부합니다. 제네릭 상속이나 멤버 타입 치환, 브리지 메서드 재생성, 제네릭 메서드 호출, 매개변수화된 내부 타입, 생성자의 숨겨진 매개변수 대응은 필요한 근거가 없을 때 계속 명시적으로 지원하지 않습니다.
클래스, 필드, 메서드, 생성자에 붙은 요소가 없고 런타임에 보이는 Java 8 @java.lang.Deprecated 마커를 보존합니다. 매개변수 어노테이션, 어노테이션이 붙은 정적 초기화 블록, 다른 가시성 수준, since나 forRemoval 등의 요소 값은 거부합니다. CI는 재컴파일 후 classfile의 Deprecated 속성과 런타임 어노테이션을 각각 비교하며, 어노테이션이 없는 대조 선언과 생성된 보조 메서드도 확인합니다. 보조 메서드는 원본 메서드 수에 포함하지 않습니다.
DEX와 smali는 클래스, 필드, 메서드, 생성자의 플랫폼 어노테이션 @android.annotation.SuppressLint도 보존합니다. build 가시성과 문자열 배열 value 하나만 허용하며, 빈 배열, 빈 문자열, 중복 값, 순서, 이스케이프된 문자 값을 유지합니다. 매개변수 어노테이션과 어노테이션이 붙은 정적 초기화 블록은 계속 지원하지 않습니다. CI는 실제 Android SDK로 재컴파일한 뒤 CLASS 보존 어노테이션과 프로그램 동작을 확인합니다.
내장 엔진은 지원되는 어노테이션 선언의 런타임에 보이는 @Retention, @Target, @Documented, @Inherited도 보존하고 실제 @interface를 출력합니다. 이 범위에서는 필드, 메서드, 타입 매개변수와 중첩 선언을 허용하지 않습니다. 이름, 접근 권한과 소속 관계를 확인할 수 있는 최상위 어노테이션 또는 정적 멤버 어노테이션을 지원하며, 지역 클래스나 익명 클래스의 범위는 거부합니다.
클래스, 인터페이스, 어노테이션 선언에 빈 마커를 적용하려면 함께 분석하는 클래스 집합 안에 접근 가능하고 일치하는 마커 정의가 있어야 합니다. 해당 Retention과 Target도 실제 적용을 허용해야 합니다. SOURCE 선언은 재구성할 수 있지만 입력에 남아 있는 적용은 거부합니다. CLASS 또는 Retention이 없는 적용은 DEX build 가시성(0), RUNTIME은 runtime 가시성(1)이 필요합니다. Retention 누락과 명시적인 CLASS, Target 누락과 빈 배열을 구분하며 Target 배열 순서를 보존합니다. Target 값은 Java 8 범위로 제한하여 MODULE, RECORD_COMPONENT 같은 최신 값은 거부합니다. @Inherited를 보존하더라도 상속된 적용을 하위 클래스의 직접 선언으로 복사하지 않습니다.
어노테이션 요소 선언과 기본값, 값이 있는 사용자 정의 어노테이션 적용, 필드·메서드·매개변수의 사용자 정의 마커, 외부 어노테이션 정의, 반복 가능한 어노테이션 컨테이너와 kotlin.Metadata는 계속 지원하지 않습니다. 마커 정의는 요소 메서드나 보조 메서드가 없는 클래스 선언으로 출력되며 복구된 메서드 수도 늘리지 않습니다.
CI는 자체 Java 8 픽스처를 D8과 NeverD로 처리한 뒤 생성된 Java 전체를 다시 컴파일하고, 전체 Signature 메타데이터와 리플렉션 결과 및 동작을 비교합니다. 이 픽스처 검사는 실제 앱의 완전한 복구를 입증하지 않습니다.
cmake --build build --target neverd
./build/bin/neverd mobile app.apk -o recovered-app공백이 있는 경로는 따옴표로 감싸세요.
& .\build\bin\neverd.exe mobile .\app.apk -o .\recovered-app다중 구성 빌드는 실행 파일을 build/bin/Release/에 배치할 수 있습니다. 해당 빌드의 일반적인 네이티브 라이브러리 배포 요구 사항을 따르세요.
아래 입력 표는 복원 작업을 설명합니다. 뒤에서 설명하는 쿼리 모드는 더 좁은 검증 범위를 적용합니다.
| 입력 | 동작 | 주요 제한 |
|---|---|---|
.apk |
전체 ZIP을 검증한 뒤 루트의 classes.dex, classes2.dex 및 이후 번호의 DEX 파일을 함께 분석 |
코드만 처리하며 리소스나 매니페스트는 디코딩하지 않음 |
.dex |
내장 판독기로 DEX 035 또는 037–040 검증 및 파싱 | DEX 041과 지원하지 않는 선언·연산은 실패하며, 이름을 바꾸거나 잘린 파일은 유효한 바이트코드가 아님 |
.smali |
제공한 클래스 분석 | 참조하는 다른 클래스를 암묵적으로 불러오지 않음 |
| smali 디렉터리 | .smali 파일을 재귀적으로 수집하여 함께 분석 |
입력 디렉터리에 중첩 클래스와 의존하는 smali 루트들을 포함해야 함 |
smali/와 smali_classes2/를 포함하는 APK 디코딩 트리를 분석하려면 두 디렉터리의 공통 상위 디렉터리를 전달합니다. 백엔드에는 .smali 파일만 전달되지만, 먼저 제공된 트리 전체를 검증하고 복사하므로 관련 없는 큰 에셋도 입력 제한에 포함됩니다. 관련 smali 루트만 모은 작은 디렉터리를 사용하면 작업량을 줄일 수 있습니다.
분할 APK는 각각 별도 입력입니다. DEX를 포함하는 APK는 개별적으로 처리할 수 있지만, 이 명령은 APK 세트를 병합하지 않습니다. 리소스만 포함한 분할 APK는 루트에 DEX가 없어 실패합니다. .aab, .apks, .xapk, .odex, .oat, .vdex는 이 mobile 명령에서 허용하는 입력이 아닙니다.
APK 리소스, AndroidManifest.xml, 에셋, JNI/네이티브 라이브러리, 실행 중 다운로드되는 코드는 Java로 복구되지 않습니다. 네이티브 .so는 별도로 추출한 뒤 neverd decompile library.so -o library.c를 사용하세요. 암호화되거나 패킹된 페이로드는 이 정적 분석 흐름에 전달하기 전에 일반 DEX/smali 형태로 준비되어 있어야 합니다. 언패킹, 기기 연결, 보호 우회는 수행하지 않습니다.
neverd mobile app.apk --list-classes
neverd mobile classes.dex --list-classes --class-prefix com.example
neverd mobile app.apk --list-classes --class-prefix Lcom/example/ --json
neverd mobile app.apk --list-classes -o classes.txt이 쿼리는 메서드 본문을 디코딩하거나 Java를 생성하지 않고 클래스 식별 정보를 읽습니다.
준비용 디렉터리나 외부 런타임이 필요하지 않습니다. 텍스트 출력은 정확한 DEX 설명자를 줄마다 하나씩 담으며,
ZIP 디렉터리 순서와 각 DEX의 정의 순서를 따릅니다. --class-prefix는 설명자 접두사 또는 점으로 구분한
패키지 접두사를 받으며 패키지 경계를 확인하지 않는 리터럴 접두사 일치를 수행합니다.
DEX 파일 간 중복을 포함한 중복 클래스 정의는 명시적으로 실패합니다. UTF-8로 손실 없이 표현할 수 없는 클래스 식별 정보도 거부합니다.
-o가 없으면 stdout에 씁니다. 쿼리 모드의 -o는 복원 디렉터리가 아닌 새 파일을 지정하며 기존 파일은 변경하지 않습니다.
선택한 모든 DEX가 성공할 때까지 결과를 버퍼에 보관하므로, 뒤의 DEX가 잘못되어도 부분 목록을 공개하지 않습니다.
--json에는 일치하는 클래스 수와 전체 클래스 수, DEX 수,
validation_scope: "dex-envelope-and-class-identities"가 포함됩니다.
모든 ZIP 이름, 헤더, 범위 및 선언된 리소스 제한을 검사합니다. 루트의 classes.dex와 번호가 붙은
classesN.dex 페이로드만 압축을 풀고 CRC를 검사하며, 관련 없는 리소스 페이로드의 무결성은 검증하지 않습니다.
각 DEX의 헤더, SHA-1, Adler-32, map 경계 및 참조된 클래스 메타데이터 검사는 유지합니다.
메서드 본문과 참조되지 않는 메타데이터는 검증하지 않습니다. 이는 목록 쿼리이며 전체 아카이브 무결성 검사나
Java 복원 증명이 아닙니다. DEX 041, method-handle 및 custom-call-site 섹션은 계속 지원하지 않습니다.
일반적인 전체 복원 경로는 여전히 모든 아카이브 페이로드를 검증합니다.
--timeout, --max-files, --max-bytes가 적용됩니다. 클래스 제한은 필터링 전에 모든 DEX의 정의를 계산하며,
선택하지 않은 ZIP 항목도 아카이브 제한에 포함됩니다. 목록 작업은 iOS 옵션 및 smali 입력을 받지 않습니다.
Java를 생성하지 않고 명령의 직접 피연산자를 찾습니다.
neverd mobile app.apk --find-refs string --query 'login failed' --json
neverd mobile classes.dex --find-refs type --query 'Lcom/example/Service;' --exact
neverd mobile app.apk --find-refs method --query '->connect(' --owner 'Lcom/example/Client;'
neverd mobile app.apk --find-refs field --query 'Lcom/example/State;->ready:Z' --exact -o refs.jsonl일치는 대소문자를 구분하는 리터럴 부분 문자열 비교입니다. --exact는 문자열 내용, 타입 설명자,
Lpkg/Type;->name(I)V와 같은 메서드 식별 정보 또는 Lpkg/Type;->name:I와 같은 필드 식별 정보 전체를 비교합니다.
--owner는 메서드 또는 필드 참조의 대상 소유자를 정확한 설명자로 제한하며, 참조를 포함하는 메서드를 필터링하지 않습니다.
빈 쿼리, smali 입력, 쿼리와 복원 옵션 혼용은 지원하지 않습니다. 정규식 메타 문자는 일반 리터럴 문자로 취급합니다.
기본 출력은 각 발생 위치당 간결한 JSON 객체 하나인 JSON Lines입니다. --json은 집계 수와 references
배열을 담은 보고서를 반환합니다. 각 행에는 dex_entry, 참조를 포함하는 전체 method,
pc_code_units(메서드의 첫 명령부터 계산한 16비트 단위), opcode, kind, target_index(DEX 내부 인덱스),
target이 기록됩니다. 여러 메서드 정의에 속하는 공유 코드도 포함해 모든 발생을 유지합니다.
문자열 행에는 정확한 target_utf16 단위도 포함되며, 단독 서로게이트 때문에 UTF-8로 손실 없이 공개할 수 없으면
target은 null입니다. 다른 식별 정보는 UTF-8로 표현할 수 있어야 합니다.
스캐너는 복원 판독기의 명령 경계, 피연산자 검사, 페이로드 처리 및 제어 흐름 검사를 사용합니다.
즉시값과 switch/array 페이로드 데이터는 참조가 될 수 없습니다. 일치하는 대상이 없더라도 정의된 모든 코드 본문을
검사한 뒤 code_scan_complete: true를 설정합니다. defined_method_count는 native 및 abstract 선언을 포함하고,
scanned_method_count는 본문이 있는 정의 수, scanned_code_item_count는 DEX별 서로 다른 물리적 본문 수입니다.
matching_pool_entries는 참조되지 않는 대상을 포함하여 일치하는 대상을 계산합니다.
validation_scope: "dex-code-references"는 DEX 외곽 구조, 식별자 테이블, 클래스/멤버 소유 관계,
읽은 코드, 예외 및 디버그 데이터를 포함합니다. 애너테이션, 정적 인코딩 값, 선언에만 있는 사용 및 참조되지 않는
메타데이터는 범위 밖입니다. Java 복원이나 ART 검증기가 아닙니다. 지원하지 않는 코드나 읽은 데이터의 형식 오류는
명시적으로 실패합니다. APK 검증은 클래스 목록과 같은 선택 페이로드 경계를 적용하며, 선택하지 않은 리소스 페이로드의 무결성은 검증하지 않습니다.
DEX 간 중복 클래스 검사까지 마친 모든 선택 DEX의 결과를 버퍼에 보관한 뒤 공개합니다. 선택적 -o는 새 파일을 지정합니다.
--max-files는 ZIP 항목뿐 아니라 누적 클래스 정의 수, 메서드 정의 수, 결과 발생 수를 각각 제한합니다.
--max-bytes는 입력, 유지되는 쿼리 데이터, 본문별 작업 저장 공간 및 출력에 적용되며 JSON 확장량을 보수적으로 반영합니다.
이는 작업 예산이며, 프로세스 RSS에는 입력 버퍼, 할당기 오버헤드 및 런타임도 포함됩니다. 일반적인 시간 및 처리량 예산도 적용됩니다.
neverd mobile app.apk -o recovered-app --platform=android \
--timeout=600 --max-files=30000 --max-bytes=4294967296 --json| 옵션 | 기본값 | 의미 |
|---|---|---|
-o PATH |
복원 시 필수 | 새 복원 디렉터리. 두 쿼리 모드에서는 선택적 새 출력 파일 |
--list-classes |
꺼짐 | Java 복원 없이 APK/DEX 클래스 식별 정보 조회 |
--class-prefix PREFIX |
모든 클래스 | 리터럴 설명자 또는 점 구분 접두사. --list-classes 필요 |
--find-refs KIND |
꺼짐 | string, type, method, field의 직접 명령 참조 조회 |
--query TEXT |
참조 조회 시 필수 | 대상 식별 정보의 리터럴 부분 문자열 |
--exact |
꺼짐 | 참조 대상 식별 정보 전체 일치 |
--owner DESCRIPTOR |
모든 소유자 | method/field 쿼리의 정확한 대상 소유자 |
--platform=auto|android |
auto |
Android를 명시적으로 선택하거나 입력에서 플랫폼을 추론 |
--timeout N |
300 |
초 단위의 양수 분석 시간 예산 |
--max-files N |
20000 |
실제로 생성되는 디렉터리를 포함한 항목 수 상한. 양수 지정 |
--max-bytes N |
2147483648 |
입력, 압축 해제 데이터, 최종 출력에 적용되는 바이트 상한. 양수 지정 |
--json |
꺼짐 | JSON 보고서 출력. 참조 쿼리는 이 옵션이 없으면 JSON Lines 출력 |
기본값 이외의 --arch, --artifact, --metadata-only, 0이 아닌 --max-func는 iOS용이므로 Android에서 거부됩니다. 명시적인 --arch=auto는 허용됩니다.
입력, 압축 해제 데이터, 최종 출력에는 파일 수와 바이트 예산이 적용됩니다. 내장 판독기와 생성기는 처리량 및 경과 시간도 검사합니다. 한 제한을 높여도 다른 제한이 해제되지 않습니다.
recovered-app/
sources/ 복원한 Java 패키지와 클래스
metadata/android-methods.json 내장 엔진의 메서드 복원 현황
report.json 버전이 명시된 목록과 복원 한계
임시 입력은 삭제됩니다. 중첩 클래스가 외부 클래스의 소스 파일을 공유할 수 있으므로 Java 파일 수는 DEX 클래스 수와 다릅니다. 생성한 메서드는 Java 디스패치 루프를 사용할 수 있습니다. 원본 DEX를 실행하거나 런타임 브리지로 호출하지 않습니다.
내장 보고서는 android_method_recovery를 포함하고 같은 내용을 metadata/android-methods.json에 저장하며 원래의 모든 메서드를 유지합니다. method_count = recovered_method_count + projected_method_count + declaration_only_method_count + unrecovered_method_count를 만족해야 합니다. 생략된 projected_method_count는 0으로 처리하며 게시 전 unrecovered_method_count도 0이어야 합니다. 원래의 native, abstract 메서드는 declaration-only 상태이며 복원한 본문 수에 포함하지 않습니다.
이름이 있고 외부 변수를 캡처하지 않는 일부 지역 클래스는 정확한 소속 static 메서드 안에 출력할 수 있습니다. 일반 스칼라 메서드, Object를 직접 상속하고 필드가 없는 클래스, 실제 무인자 생성자, 스칼라 인스턴스 메서드, 지원 범위 밖으로 유출되지 않는다고 검증된 객체 사용이 필요합니다. 익명 클래스, 변수 캡처, 지원하지 않는 제한자와 입증되지 않은 사용은 계속 명시적으로 실패합니다.
이 출력에서 지역 클래스 메서드와 소속 메서드는 source-projected, projection_kind: "named-method-local"로 기록합니다. 바깥 처리 보고서가 success여도 메서드 복원 현황은 partial입니다. 재컴파일 후의 바이너리 이름과 접근 플래그는 모두 아직 검증되지 않았습니다. Java 컴파일러가 다른 바이너리 이름을 선택할 수 있으므로 class_source_bindings는 원래 클래스, 정확한 소속 메서드, 소스 경로와 지역 이름을 보존하고 binary_name_status를 unverified로 표시합니다.
generated_source_helpers는 추가 메서드를 나열하며 정확한 종류는 throw-helper, constant-helper, default-constructor, field-initializer입니다. 마지막 종류는 원래 메서드 목록에 없었으며 추가로 생성된 <clinit>을 뜻합니다. 이 추가 메서드들은 원래 메서드 총수에 포함하지 않습니다. 컴파일 성공이나 한 번의 이름 일치로 완전 복원 상태가 되지는 않습니다. 아래 축약 예시는 투영 메서드를 포함하지 않습니다.
{
"schema_version": 1,
"status": "success",
"platform": "android",
"source": "app.apk",
"input_kind": "apk",
"backend": {
"name": "neverd",
"version": "1",
"execution": "builtin"
},
"input_code_files": [
"classes.dex",
"classes2.dex"
],
"dex_count": 2,
"smali_count": 0,
"java_source_count": 2,
"java_sources": [
"sources/example/Main.java",
"sources/example/Peer.java"
],
"logs": [],
"android_method_recovery": {
"schema_version": 1,
"status": "recovered",
"class_count": 2,
"method_count": 6,
"recovered_method_count": 5,
"declaration_only_method_count": 1,
"unrecovered_method_count": 0
}
}input_kind는 apk, dex, smali, smali-directory 중 하나입니다. input_code_files에는 입력 바이트코드 이름이나 smali 경로가 나열되며, java_sources와 logs는 출력 루트 기준 상대 경로입니다. source는 입력의 기본 이름입니다. 실제 보고서에는 추가적인 재구성 제한 사항도 포함됩니다. 결과를 다른 도구에 전달할 때도 이 정보를 보존하세요.
자동화에서는 status를 사용하기 전에 프로세스 종료 코드를 확인합니다. 표준 출력을 리디렉션할 때는 보고서를 새 출력 디렉터리 밖에 저장하세요.
neverd mobile app.apk -o recovered-app --json > recovery-result.json네이티브 CLI는 성공 시 0, 복구 실패 시 0이 아닌 값을 반환합니다. --json은 처리된 실패에 schema_version, status: "error", error를 포함합니다. 인수 분석, 네이티브 실행 파일이나 라이브러리 시작 실패, 중단은 stderr로만 보고될 수 있습니다. 호출자는 먼저 종료 상태를 확인해야 합니다.
결과 게시는 트랜잭션 방식으로 이루어집니다. 기존 출력은 보존하고 실패한 임시 출력은 삭제합니다. 지원하지 않는 연산, 해결하지 못한 레지스터 흐름, 표현할 수 없는 선언, 잘못된 예외 처리, 예산 소진은 내장 실행을 실패하게 하며 누락된 본문을 게시하지 않습니다. 복원 성공은 의미적 동등성의 증명이 아닙니다.
| 증상 | 조치 |
|---|---|
| 지원하지 않는 DEX·명령·선언·초기화 | 명시적 진단과 지원 범위를 확인 |
| 유효하지 않은 입력 또는 중복 클래스 | 입력 바이트코드나 클래스 구성을 수정. 지원하지 않는 본문은 조용히 생략되지 않음 |
| 시간 또는 자원 예산 초과 | 입력을 줄이거나 가용 자원에 맞게 --timeout, --max-files, --max-bytes 조정 |
| 출력이 이미 존재 | 새로운 출력 디렉터리 선택 |
Python은 아래 개발 테스트 스크립트에만 사용합니다. 내장 모바일 복구는 네이티브 C++20 CLI에서 실행됩니다.
cmake --build build --target check-neverd-mobile
ctest --test-dir build -L NeverDMobileTests --output-on-failure
python3 scripts/test_mobile_android_internal.py --d8 PATH --neverd build/bin/neverd컴포넌트와 CLI 테스트는 파싱, 출력 계약, 실패 시 정리를 확인합니다. 내장 실행 비교 러너는 JDK(java, javac)와 D8로 독립 DEX/APK 예제를 만들고 복원한 Java를 컴파일·실행합니다. 이는 테스트 의존성이며 내장 복원의 실행 요구 사항이 아닙니다. 현재 빌드로 실행하고 결과를 확인한 뒤에만 해당 사례를 검증했다고 판단하세요. 예제 성공은 임의 애플리케이션의 완전한 복원을 입증하지 않습니다.
관련 iOS 작업 흐름은 모바일 개요를 참고하세요.