Community라이팅 & 에디팅github.com

Marukon/skill-upgrade-libxposed-102

LibXposed API 102로 Xposed 모듈 업그레이드 가이드

skill-upgrade-libxposed-102란 무엇인가요?

이 스킬은 기존 XposedBridge API 기반의 전통적인 Xposed 모듈을 최신 LibXposed API 102(LSPosed 1.9.3+, Vector 등 현대 프레임워크)로 업그레이드하는 완전한 가이드를 제공합니다. 네 가지 주요 마이그레이션 단계를 통해 종속성, 메타데이터, 매니페스트 및 후킹 로직을 변환합니다. LibXposed는 체인 인터셉터 기반의 'Hooker'를 사용하여 후킹을 처리하고, 핫 리로드를 기본적으로 지원하며, Zygote 주입을 금지합니다. 또한 성능 최적화(StackWalker, 제로 오버헤드 패스트 패스)와 일반적인 함정(예: assets/xposed_init 유지, Gradle 호환성)도 다룹니다. 이 스킬은 Claude Code, Cursor, Codex와 같은 AI 에이전트가 모듈 업그레이드를 자동화하는 데 이상적입니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add Marukon/skill-upgrade-libxposed-102

Installed? Explore more 라이팅 & 에디팅 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Xposed 모듈 업그레이드: LibXposed API 102 완벽 가이드

이 문서는 기존 XposedBridge API 54-93 기반의 전통적인 Xposed 모듈을 최신 LibXposed API 102(LSPosed 1.9.3+, Vector 등 현대 프레임워크)로 업그레이드하는 표준 절차, 코드 패러다임, 메타데이터 규칙 및 성능 최적화 전략을 정리합니다.

향후 다른 모듈에 대한 업그레이드 지침도 이 규칙을 따릅니다.


1. 핵심 변경 사항 비교

특성 / 차원전통적인 Xposed (API 54-93)현대 LibXposed (API 102)
종속성 좌표de.robv.android.xposed:api:82 (또는 93)io.github.libxposed:api:102.0.0
진입 베이스 클래스/인터페이스IXposedHookLoadPackage, IXposedHookZygoteInitio.github.libxposed.api.XposedModule 상속
진입 선언assets/xposed_initMETA-INF/xposed/java_init.list
모듈 속성 설정AndroidManifest.xml의 <meta-data>META-INF/xposed/module.prop
범위 선언xposedscope 리소스 배열META-INF/xposed/scope.list
Hook 프로그래밍 모델XC_MethodHook (before / after) 콜백OkHttp 스타일 체인 인터셉터 Hooker (chain.proceed())
핫 리로드 (Hot Reload)지원 안 함 (대상 앱 또는 시스템 재시작 필요)기본 지원 (onHotReloading / onHotReloaded 오버라이드)
Zygote 주입initZygote 전역 주입 지원Zygote 주입 금지, 범위 내 프로세스에만 주입
난독화/R8 지원Hook 메서드명과 클래스명 엄격히 보존 필요R8 난독화 기본 지원, 적절한 규칙만 선언하면 됨

2. 표준 마이그레이션 4단계

1단계: 빌드 및 종속성 마이그레이션 (build.gradle)

  1. 종속성 업데이트: 모듈의 build.gradle(예: app/build.gradle 또는 hook/build.gradle)에서 종속성을 교체합니다.

    dependencies {
        // 제거: compileOnly 'de.robv.android.xposed:api:82'
        // 현대 LibXposed 102 API 도입
        compileOnly 'io.github.libxposed:api:102.0.0'
        compileOnly 'androidx.annotation:annotation:1.5.0'
    }
    
  2. Java 컴파일 호환성:

    android {
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_11
            targetCompatibility JavaVersion.VERSION_11
        }
    }
    
  3. ProGuard / R8 규칙 설정 (proguard-rules.pro):

    -dontwarn io.github.libxposed.annotation.**
    -adaptresourcefilecontents META-INF/xposed/java_init.list
    -keep,allowoptimization,allowobfuscation public class * extends io.github.libxposed.api.XposedModule {
        public <init>();
    }
    

2단계: 현대 메타데이터 파일 설정 (META-INF/xposed/)

모듈 소스 디렉터리 src/main/resources/META-INF/xposed/ 아래에 다음 파일을 생성합니다.

1. module.prop (모듈 메타데이터)

minApiVersion=102
targetApiVersion=102
autoHotReload=true
exceptionMode=protective
  • minApiVersion: 지원되는 최소 API 버전, 102로 설정.
  • targetApiVersion: 대상 API 버전, 102로 설정.
  • autoHotReload: true로 설정하면 모듈 업데이트 설치 시 자동으로 핫 리로드가 트리거됩니다.
  • exceptionMode: protective(기본 보호 모드, Hook 예외가 호스트 앱을 크래시시키지 않음) 또는 passthrough.

2. java_init.list (진입 클래스 선언)

모듈 진입 전체 클래스명을 한 줄로 선언:

com.example.myhook.MainHook

3. scope.list (권장 범위)

한 줄에 하나의 대상 패키지 이름을 지정하여 모듈이 기본적으로 주입할 앱을 선언합니다.

com.android.chrome
com.microsoft.emmx
com.example.targetapp

3단계: 매니페스트 파일 설정 (AndroidManifest.xml)

모듈의 AndroidManifest.xml을 업데이트합니다.

<application ...>
    <!-- 기존 메타데이터 유지, 중요: xposedminversion을 102로 설정 -->
    <meta-data
        android:name="xposedmodule"
        android:value="true" />
    <meta-data
        android:name="xposeddescription"
        android:value="모듈 기능 설명" />
    <meta-data
        android:name="xposedminversion"
        android:value="102" />
    <meta-data
        android:name="xposedscope"
        android:resource="@array/xposed_scope" />
</application>

4단계: Hook 로직 재구성 (XposedModule)

1. 기본 템플릿과 생명주기

package com.example.myhook;

import android.util.Log;
import androidx.annotation.NonNull;
import io.github.libxposed.api.XposedInterface;
import io.github.libxposed.api.XposedModule;

public class MainHook extends XposedModule {

    private static final String TAG = "MyHookModule";
    private volatile boolean mHooked = false;

    public MainHook() {
        super();
    }

    /**
     * 호스트 앱 ClassLoader가 준비되고 Application이 생성되기 직전에 트리거됩니다.
     */
    @Override
    public void onPackageReady(@NonNull PackageReadyParam param) {
        if (mHooked) return;
        synchronized (this) {
            if (mHooked) return;
            mHooked = true;
            initHooks(param.getPackageName(), param.getClassLoader());
        }
    }

    /**
     * 핫 리로드 준비 콜백 (이전 버전 코드에서 실행)
     * true를 반환하면 프레임워크가 새 버전으로 교체하도록 허용합니다.
     */
    @Override
    public boolean onHotReloading(@NonNull HotReloadingParam param) {
        return true;
    }

    /**
     * 핫 리로드 완료 콜백 (새 버전 코드에서 실행)
     */
    @Override
    public void onHotReloaded(@NonNull HotReloadedParam param) {
        // 이전 버전에서 등록된 모든 Hook 해제
        param.getOldHookHandles().forEach(XposedInterface.HookHandle::unhook);
        synchronized (this) {
            mHooked = false;
            initHooks("hot-reload", getClass().getClassLoader());
        }
    }

    private void initHooks(String packageName, ClassLoader classLoader) {
        // 구체적인 Hook 로직 실행
    }
}

2. 핵심 Hook 문법 대조

대상 작업전통적인 Xposed 방식LibXposed 102 현대 방식
메서드 찾기 및 HookXposedHelpers.findAndHookMethod("클래스명", cl, "메서드", argTypes..., callback)Method m = Clazz.class.getDeclaredMethod("메서드", argTypes...);hook(m).intercept(chain -> { ... });
원본 메서드 실행기본적으로 직접 실행; param.getResult() 호출Object result = chain.proceed();
인자 수정 후 호출param.args[0] = newVal;Object result = chain.proceed(new Object[]{ newVal, ... });
가로채기 및 반환값 교체param.setResult(customVal);사용자 정의 값 직접 반환 (chain.proceed() 호출하지 않음):hook(m).intercept(chain -> customVal);
입력 인자 가져오기param.args[i]chain.getArg(i) 또는 chain.getArgs()
this 인스턴스 가져오기param.thisObjectchain.getThisObject()
생성자 HookfindAndHookConstructor(...)Constructor<?> ctor = ...;hook(ctor).intercept(chain -> ...);
정적 초기화 블록 Hook전통적인 API에서 표준 지원 안 함hookClassInitializer(TargetClass.class).intercept(...)

3. 고빈도 Hook 시나리오 및 성능 심층 최적화 패턴

고빈도 호출(예: isEnabled, View 드로잉, 터치 디스패치)에 대한 황금 최적화 규칙:

  1. 제로 오버헤드 고속 경로 (스니핑 패스스루): 메서드가 상태 조회 메서드(예: isEnabled(), isLoaded())인 경우, 먼저 가벼운 네이티브 필드 읽기 chain.proceed()를 호출합니다. 실제 상태가 거짓이면, 후속 로직과 스택 검사를 모두 건너뛰고 바로 거짓을 반환합니다.

  2. Android 14+ (API 34+)에서 StackWalker 지연 순회 사용: 전통적인 new Throwable().getStackTrace()는 전체 스레드 스택 깊이 확장(Native Unwind)과 수백 개의 문자열 할당을 유발하여 GC 드랍프레임을 쉽게 일으킵니다. Java 9 / Android 14+의 java.lang.StackWalker를 사용하여 필요할 때만 계층별로 스캔하며, 전체 스택 할당이 없습니다.

  3. Android < 34에서 동적 스택 위치 찾기 (하드코딩 금지): 한 번만 new Throwable().getStackTrace()를 실행하고, 아래로 순회하며 대상 메서드가 속한 시스템 클래스(예: AccessibilityManager)를 찾은 후, 바로 다음 스택 프레임이 실제 호출자입니다. [3] 또는 [4]와 같은 하드코딩을 절대 사용하지 않아 LSPosed 버전 간 안정적인 실행을 보장합니다.

성능 최적화 예시 코드:

// Android 14+ StackWalker 확인
private static final boolean HAS_STACK_WALKER;
static {
    boolean has;
    try {
        Class.forName("java.lang.StackWalker");
        has = (Build.VERSION.SDK_INT >= 34);
    } catch (Throwable t) {
        has = false;
    }
    HAS_STACK_WALKER = has;
}

// 고빈도 메서드 Hook 예시
Method isEnabled = AccessibilityManager.class.getDeclaredMethod("isEnabled");
hook(isEnabled).intercept(chain -> {
    Object real = chain.proceed();
    if (Boolean.FALSE.equals(real)) {
        return false; // 시스템이 이미 꺼져 있음, 제로 오버헤드 직통 반환
    }
    return isCallerWhitelisted() ? real : false;
});

private static boolean isCallerWhitelisted() {
    if (HAS_STACK_WALKER) {
        try {
            return Api34StackWalker.check();
        } catch (Throwable ignored) {}
    }
    return checkLegacy();
}

@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private static final class Api34StackWalker {
    private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);

    static boolean check() {
        return Boolean.TRUE.equals(WALKER.walk(frames -> {
            boolean foundTarget = false;
            var it = frames.iterator();
            while (it.hasNext()) {
                var f = it.next();
                if ("android.view.accessibility.AccessibilityManager".equals(f.getClassName())) {
                    foundTarget = true;
                } else if (foundTarget) {
                    return isPackageAllowed(f.getClassName());
                }
            }
            return false;
        }));
    }
}

4. 업그레이드 흔한 함정 및 문제 해결 체크리스트

  1. assets/xposed_init를 계속 유지해야 하나요?
    • 현대 API 102는 META-INF/xposed/java_init.list만 읽습니다. assets/xposed_init를 유지하면 구형 프레임워크가 전통적인 리플렉션 방식으로 로드하려고 시도하여 오류가 발생할 수 있습니다. 제거하거나 비워 두는 것이 좋습니다.
  2. compileSdk와 Android SDK 플랫폼:
    • 최신 인터페이스와 Java 11/17 언어 기능을 원활하게 사용하려면 compileSdk와 targetSdk를 Android 33 또는 34로 업그레이드하는 것이 좋습니다.
  3. Gradle 호환성:
    • 빌드 환경이 JDK 17/21을 사용하는 경우, Gradle Wrapper를 7.4+(권장 7.5.1 또는 8.x)로, AGP를 7.3+로 업그레이드해야 합니다.
  4. 모듈 인식 확인:
    • 압축 해제 도구를 사용하여 최종 APK 루트 디렉터리에 다음 파일이 있는지 확인하세요:
      • META-INF/xposed/module.prop
      • META-INF/xposed/java_init.list
      • META-INF/xposed/scope.list

관련 스킬