Android System & AOSP Engineering/AOSP Framework & Custom Services

Android System API 사용법과 Hidden API 접근 우회 및 커스텀 가이드

임베디드 친구 2025. 4. 16. 13:59
반응형

안녕하세요! 개발하는 머리입니다. 안드로이드 애플리케이션을 만들다 보면 구글 공식 문서(SDK)에는 나오지 않는 OS 내부의 강력한 기능을 쓰고 싶을 때가 있습니다. 예를 들면 일반 앱은 접근할 수 없는 시스템 설정을 강제로 바꾼다거나, 시스템 내부의 비공개 스레드 상태를 조회하는 일이죠.

안드로이드 프레임워크 내부에는 이처럼 일반 개발자에게 숨겨진 Hidden API와 디바이스 제조사나 시스템 앱 전용으로 설계된 System API가 존재합니다. 과거에는 자바 리플렉션(Reflection)만으로 쉽게 가져다 썼지만, 최신 안드로이드 OS는 보안을 이유로 이 통로를 단단히 잠그고 있습니다. 이번 포스팅에서는 이 비공개 API들의 차이점을 명확히 짚어보고, 최신 버전에서의 접근 우회 방식 및 AOSP 환경에서 나만의 System API를 직접 추가하는 정석적인 방법까지 공유해 보겠습니다.

Generated by Gemini AI.

📌 핵심 요약 3줄

  1. Hidden API는 SDK에서 감춰진 내부 자바 멤버들이며, System API는 @SystemApi 어노테이션이 붙은 권한(Privileged) 앱 전용 API입니다.
  2. 안드로이드 9(Pie) 버전 이후부터는 ART 런타임 레벨에서 Hidden API 사용을 강하게 차단하므로, 단순 리플렉션 외에 추가적인 우회 기법이 필요합니다.
  3. 커스텀 System API를 빌드하려면 AOSP 소스 트리에서 AIDL 인터페이스 정의와 서비스 등록을 마치고 stub 라이브러리를 갱신해 주어야 합니다.

1. Hidden API와 System API의 명확한 차이점

두 개념은 '공식 SDK에 노출되지 않는다'는 공통점이 있어 혼동하기 쉽지만, 설계 목적과 접근 제어 메커니즘이 완전히 다릅니다. 이 차이를 표로 먼저 정리해 보겠습니다.

구분 Hidden API System API
코드상 표기 소스코드 자바독에 /** @hide */ 태그 명시 자바독 태그와 함께 @SystemApi 어노테이션 부여
설정 목적 SDK 하위 호환성 유지 및 개발자 변조 방지 (내부 구현용) 제조사(OEM), 통신사, 시스템 서비스 앱 전용 기능 제공
일반 앱 접근성 기본 차단 (런타임 제약 우회 시 접근 가능) 절대 불가 (플랫폼 서명 또는 Privileged 권한 필수)
대표적인 예시 android.app.ActivityThread, VMRuntime 등 Settings.Global.putInt(), 멀티 유저 제어 API 등
배포 라이브러리 공식 SDK 빌드 시 텍스트 Stub에서 완전 제외 확장 SDK(system-stubs 등의 형태)로 파트너에게 제공

2. 숨겨진 API(Hidden API) 접근 및 제한 우회

2.1 Java 리플렉션을 이용한 전통적인 접근

과거 안드로이드 초기 버전부터 자주 쓰던 방식으로, 런타임에 클래스 정보를 강제로 읽어와 숨겨진 메서드를 호출합니다.

Java
 
import java.lang.reflect.Method;

public class HiddenApiAccess {
    public static void accessActivityThread() {
        try {
            // 숨겨진 클래스인 ActivityThread의 메타정보를 로드합니다.
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            
            // 접근 제한을 강제로 해제합니다.
            currentActivityThreadMethod.setAccessible(true);
            Object at = currentActivityThreadMethod.invoke(null);
            
            System.out.println("성공적으로 ActivityThread 인스턴스를 획득했습니다: " + at);
        } catch (Exception e) {
            e.printStackTrace(); // 안드로이드 9.0 이상에서는 Access 런타임 에러가 발생할 수 있습니다.
        }
    }
}

2.2 VMRuntime을 활용한 런타임 정책 우회

구글은 안드로이드 9(Pie)부터 ART(Android Runtime) 내부 리스트(Blacklist/Greylist)를 기반으로 리플렉션을 차단하기 시작했습니다. 이를 무력화하기 위해 내부 가상머신 통제 클래스인 VMRuntime에 exemptions(예외 목록)을 강제로 밀어 넣는 편법이 개발되었습니다.

Java
 
import dalvik.system.VMRuntime;
import java.lang.reflect.Method;

public class HiddenApiBypass {
    public static void disableHiddenApiRestriction() {
        try {
            // VMRuntime의 예외 등록 메서드를 가져옵니다.
            Method setHiddenApiExemptions = VMRuntime.class.getDeclaredMethod("setHiddenApiExemptions", String[].class);
            setHiddenApiExemptions.setAccessible(true);
            
            // "L"을 넘겨 모든 시그니처 프리픽스에 대해 제한을 면제(Exemption)해 줍니다.
            setHiddenApiExemptions.invoke(VMRuntime.getRuntime(), new Object[]{new String[]{"L"}});
        } catch (Exception e) {
            e.printStackTrace(); 
        }
    }
}

⚠️ 경고: 최신 안드로이드 버전(13~15 이상) 환경에서는 이 VMRuntime 우회 코드조차 내부 부트 클래스로더 검증 및 SELinux 정책에 의해 차단되는 경우가 많습니다. 상용 앱 서비스에서 이 방식에 의존하는 것은 매우 위험합니다.


3. System API를 활용한 기능 확장 및 권한 설정

진짜 @SystemApi로 지정된 기능을 활용하려면 앱이 일반 서드파티 앱 권한을 넘어서야 합니다. 안드로이드 단말 내부의 특수 폴더에 위치하거나 시스템 권한을 획득해야 원활한 동작이 보장됩니다.

3.1 AndroidManifest.xml 권한 및 공유 UID 정의

시스템 설정을 임의로 바꾸기 위해서는 시스템 공유 ID를 사용하고 보안 설정 권한을 요청해야 합니다.

XML
 
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.systemapi"
    <!-- 시스템 내부 권한 앱들과 UID를 공유하여 강력한 권한을 공유받습니다. -->
    android:sharedUserId="android.uid.system">

    <!-- 시스템 설정 변경에 필요한 권한 선언 -->
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>

    <application>
        <!-- 시스템 라이브러리 사용 명시 -->
        <uses-library android:name="android.ext.services" />
    </application>
</manifest>

주의: android:sharedUserId="android.uid.system"을 선언한 앱은 OS 빌드 시 사용된 **플랫폼 서명 키(Platform Signing Key)**로 사인되어 빌드되거나 디바이스의 /system/priv-app/ 경로에 설치되어야만 정상 구동됩니다.

3.2 System API 실전 활용 (시스템 비행기 모드 강제 전환)

권한을 획득했다면 일반 앱에서는 에러를 뿜는 Settings.Global API를 통해 기기 전역 설정을 제어할 수 있습니다.

Java
 
import android.content.ContentResolver;
import android.provider.Settings;

public class SystemSettingsChanger {
    public static void toggleAirplaneMode(ContentResolver contentResolver, boolean turnOn) {
        // 일반 앱이 호출하면 SecurityException이 발생하는 시스템 API입니다.
        Settings.Global.putInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, turnOn ? 1 : 0);
    }
}

4. AOSP 환경에서 커스텀 System API 추가하기

직접 안드로이드 OS 커스텀 펌웨어를 만드는 프레임워크 엔지니어라면 나만의 커스텀 System API를 프레임워크 레이어에 직접 심어줄 수 있습니다.

4.1 AIDL 인터페이스 정의 (IMyCustomService.aidl)

프로세스 간 장벽을 넘기 위해 frameworks/base/core/java/android/os/IMyCustomService.aidl 경로에 인터페이스를 정의합니다.

Java
 
package android.os;

/** @hide */
interface IMyCustomService {
    void myCustomMethod();
}

4.2 자바 서비스 레이어 구현 및 시스템 API 어노테이션 부여

인터페이스를 구현할 서비스를 작성할 때 하위 앱 개발자에게 제공할 진입 메서드에 @SystemApi 어노테이션을 선언합니다.

Java
 
package com.android.server;

import android.annotation.SystemApi;
import android.os.IMyCustomService;

public class MyCustomService extends IMyCustomService.Stub {
    
    // 시스템 권한을 가진 파트너 앱 개발자에게 공식 개방하는 API로 선언합니다.
    @SystemApi
    @Override
    public void myCustomMethod() {
        // 커스텀 보드의 GPIO 제어나 하드웨어 특화 로직 실행
    }
}

4.3 빌드 시스템 반영 및 API 업데이트

AOSP 프레임워크에 자바 코드나 AIDL 구조를 변경했다면 단순 빌드를 돌리면 에러가 납니다. 아래 명령어를 통해 변경된 API 시그니처 텍스트 목록(current.txt)을 업데이트해 준 뒤 컴파일을 진행해야 합니다.

Bash
 
# 변경된 API 시그니처를 검증하고 빌드 시스템에 업데이트합니다.
make update-api

# 프레임워크 모듈을 집중 빌드합니다.
make -j8

# 디바이스에 동기화 및 반영
adb remount
adb sync
adb reboot

🛠️ 개발을 위한 팁 (Tips)

  1. ndk_crux 및 네이티브 레이어 고려: 자바 레벨에서 리플렉션을 우회하는 기법은 한계가 명확합니다. 조금 더 확실한 시스템 내부 데이터 접근이 필요하다면 자바 레이어의 우회책 대신 NDK(C/C++) 레이어에서 dlopen()을 활용해 시스템 공유 라이브러리(.so)의 심볼을 직접 찾아 호출하는 방식이 런타임 제약조건을 피하는 데 훨씬 유리합니다.
  2. system-stubs 빌드 아티팩트 활용: 커스텀 System API를 성공적으로 빌드했다면 프레임워크 소스 안에 생성된 android.system.stubs.jar 파일을 추출하여 상위 앱 개발 팀에게 전달해 주세요. 일반 스튜디오 환경에서 이 jar 파일을 라이브러리로 링크하면 컴파일 타임에 빨간 줄(에러) 없이 깔끔하게 개발을 진행할 수 있습니다.

⚠️ 흔히 하는 실수 (Common Mistakes)

  1. 릴리즈 앱에서의 Hidden API 의존: 무심코 오픈소스 라이브러리를 가져다 썼는데 그 내부에 VMRuntime 등의 Hidden API 우회 코드가 포함되어 있는 경우가 있습니다. 구글 플레이스토어 심사 시스템은 정적 분석을 통해 비공개 API 사용 여부를 칼같이 잡아내며, 최악의 경우 앱 출시 거부 사유가 되므로 상용 서비스 앱 빌드 시에는 철저히 배제해야 합니다.
  2. make update-api 누락으로 인한 빌드 크래시: AOSP 환경에서 자바 파일에 @SystemApi를 새로 붙이거나 수정한 뒤 이 명령어를 수행하지 않고 make를 치면, 빌드 시스템이 "정의되지 않은 부적절한 API가 발견되었다"면서 컴파일을 의도적으로 중단시킵니다. 프레임워크 시그니처 수정 후에는 무조건 update-api를 실행하는 습관을 지니는 것이 정신 건강에 좋습니다.

5. 결론

안드로이드 플랫폼의 Hidden API와 System API 시스템은 운영체제의 보안 안정성을 해치지 않으면서도 제조사나 코어 시스템 서비스에게 확장성을 열어주기 위해 설계된 정교한 장치입니다.

애플리케이션 레이어 개발자라면 플레이스토어 정책과 미래 버전 호환성을 위해 최대한 공식 SDK 안에서 해결하는 안목이 필요하며, 플랫폼 프레임워크 엔지니어라면 정석적인 AIDL 설계와 API 선언 정책을 준수하여 시스템의 뼈대를 튼튼하게 구축해야 합니다. 프레임워크 내부 커스텀을 시도하시는 분들에게 좋은 이정표가 되었기를 바랍니다. 질문이 있다면 언제든 댓글을 달아주세요!

반응형