모바일 앱의 Root Detection — 탐지 방법 및 작동 원리

저자: IT Sectr 게시일: 2026-04-03 읽는 시간: 9 분

Root Detection은 슈퍼유저 권한이 있는 기기에서 Android 애플리케이션이 실행되는 것을 방지하는 보안 메커니즘입니다. 은행, 결제 및 기업용 애플리케이션은 루팅된 기기에서 기능을 차단하거나 제한합니다. 루트 액세스는 Android 샌드박스의 제한을 제거하고 트래픽 가로채기, 프로세스 메모리 읽기, 데이터 변조 가능성을 열어주기 때문입니다. OWASP Mobile Top 10 (2024)에 따르면, Root Detection의 부재는 M8 카테고리(Security Decisions via Untrusted Inputs)에 해당합니다. Root Detection은 파일 시스템의 정적 검사와 런타임 동작의 동적 분석의 조합을 기반으로 합니다.

핵심 요점

  • Root Detection — 손상된 환경에서 실행되지 않도록 애플리케이션을 보호하기 위해 Android 기기의 루트 액세스 확인
  • 정적 메서드는 su 바이너리, Superuser 앱 및 시스템 파티션 변경에 대해 파일 시스템을 확인
  • 동적 메서드는 런타임 분석: Build.TAGS 확인, 특권 프로세스로 /proc/self/maps 열기 시도
  • 네이티브 구현은 JNI를 통한 C/C++로 Java 수준에서 Xposed 및 Frida를 통한 우회에 대한 저항성 제공
  • 보안 Root Detection은 검사 결과 조작을 방지하기 위해 지속적인 난독화와 서버 측 검증 필요

Root Detection이란?

Root Detection은 Android 기기에서 루트 액세스의 존재를 감지하는 소프트웨어 메커니즘입니다. 루트 액세스는 운영 체제를 완전히 제어하여 애플리케이션과 스크립트가 UID 0으로 명령을 실행할 수 있도록 합니다. 루팅된 기기에서는 애플리케이션 격리(Android Sandbox)가 손실되어 키보드 입력 가로채기, 다른 앱의 SQLite 데이터베이스 읽기, 프로세스에 코드 주입, 신뢰할 수 있는 저장소의 SSL 인증서 교체가 가능해집니다.

금융 및 기업용 애플리케이션의 경우, 루팅된 기기에서 실행하는 것은 허용할 수 없는 위험을 나타냅니다. 공격자가 토큰, 세션 키 및 개인 데이터에 액세스할 수 있습니다. PCI Security Standards Council을 포함한 규제 기관은 결제 애플리케이션이 루트 액세스를 감지하고 대응하도록 요구합니다. 이에 대응하여 Android 개발자는 사전 예방적 보호 전략의 일부로 Root Detection을 내장합니다.

탐지에는 두 가지 접근 방식이 있습니다. 파일 시스템과 설치된 패키지를 분석하는 정적 방식과 런타임에서 검사를 수행하는 동적 방식입니다. 결합된 접근 방식이 가장 신뢰할 수 있는 것으로 간주되는데, 다양한 우회 벡터를 커버하기 때문입니다. NowSecure(2025)의 연구에 따르면 Google Play 상위 100개 은행 앱 중 76%가 어떤 형태의 Root Detection을 포함하고 있습니다.

루트 탐지의 정적 메서드

정적 메서드는 애플리케이션 시작 시 실행되며 루팅 도구가 파일 시스템에 남긴 루트 액세스의 징후를 확인합니다. 이러한 메서드는 특권 명령 실행이 필요하지 않으며 일반 애플리케이션의 컨텍스트에서 작동합니다.

su 바이너리 존재 확인

루트 액세스의 주요 지표는 표준 경로에 su 실행 파일이 존재하는지 여부입니다: /system/bin/su, /system/xbin/su, /sbin/su, /su/bin/su. 애플리케이션은 File.exists() 또는 libc의 access() 네이티브 구현을 통해 파일 존재를 확인합니다. 추가로 su --version 또는 su -c id를 실행하고 종료 코드를 확인할 수 있습니다.

루트 관리자 검색

루트 액세스 관리를 위한 일반적인 애플리케이션: Superuser, SuperSU, Magisk Manager, KingRoot. 이들의 존재는 PackageManager.getPackageInfo()를 통해 또는 /data/app/ 디렉토리를 읽어 확인합니다. 확인할 패키지: com.topjohnwu.magisk, eu.chainfire.supersu, com.noshufou.android.su, com.thirdparty.superuser, com.koushikdutta.superuser, com.zacharee1.systemuituner.

시스템 속성 확인

Android는 System.getProperty 및 Build.TAGS를 통해 액세스 가능한 시스템 속성에 시스템 상태 정보를 저장합니다. Build.TAGS가 release-keys 대신 test-keys를 포함하는 경우, 이는 사용자 정의 펌웨어를 나타내며 종종 루트 액세스가 있습니다. 추가로 /system/build.prop을 읽어 ro.build.tags, ro.debuggable 및 ro.secure를 확인합니다.

java
public class RootDetectionChecker {
    private static final String[] SU_PATHS = {
        "/system/bin/su",
        "/system/xbin/su",
        "/sbin/su",
        "/su/bin/su",
        "/system/sd/xbin/su"
    };

    public boolean checkRootByFiles() {
        for (String path : SU_PATHS) {
            if (new File(path).exists()) {
                return true;
            }
        }
        return false;
    }

    public boolean checkRootByPackages(Context ctx) {
        String[] packages = {
            "com.topjohnwu.magisk",
            "eu.chainfire.supersu",
            "com.noshufou.android.su",
            "com.koushikdutta.superuser"
        };
        for (String pkg : packages) {
            try {
                ctx.getPackageManager().getPackageInfo(pkg, 0);
                return true;
            } catch (PackageManager.NameNotFoundException e) {
                // package not found
            }
        }
        return false;
    }
}

검증의 동적 메서드

동적 메서드는 애플리케이션 작동 중에 실행되며 실행 환경을 분석합니다. 정적 메서드와 달리 Magisk Hide나 Zygisk를 통해 숨겨진 루팅을 감지할 수 있는데, 이는 파일 구조뿐만 아니라 시스템 동작을 확인하기 때문입니다.

마운트 포인트 확인

루트 액세스가 있으면 일부 시스템 파티션이 ro(읽기 전용) 대신 rw(읽기-쓰기) 플래그로 마운트됩니다. 애플리케이션은 /proc/mounts를 읽고 /system이 ro로 마운트되었는지 확인합니다. /system이 rw로 마운트된 경우, 이는 수정된 시스템을 나타냅니다. 추가로 Magisk를 통한 /su 마운트 존재 여부도 확인됩니다.

안전 모드 테스트

Android 안전 모드는 루트 관리자를 포함한 타사 애플리케이션을 비활성화합니다. 올바르게 구현된 Root Detection은 기기가 안전 모드에서 실행 중인지 확인할 수 있습니다. 애플리케이션이 루트 관리자는 보이지 않지만 su 바이너리는 존재한다고 감지하면, 이는 Magisk Hide의 지표입니다.

명령 실행 확인

ProcessBuilder 또는 Runtime.exec를 통해 su -c id를 실행하려는 시도는 루트 액세스의 직접적인 테스트입니다. 그러나 Magisk가 이 호출을 가로챌 수 있습니다. 더 신뢰할 수 있는 접근 방식은 네이티브 코드를 통한 확인입니다: /proc/1/limits 또는 /proc/self/maps를 열고 실행 중인 프로세스의 UID를 분석합니다. 애플리케이션이 UID 0을 얻거나 루트만 접근 가능한 파일을 읽을 수 있다면 기기가 손상된 것입니다.

java
public boolean checkRootDynamically() {
    // Build flags check
    String buildTags = Build.TAGS;
    if (buildTags != null && buildTags.contains("test-keys")) {
        return true;
    }

    // Checking /system mount
    try {
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(new FileInputStream("/proc/mounts"))
        );
        String line;
        while ((line = reader.readLine()) != null) {
            if (line.contains("/system")
                && line.contains("rw")) {
                reader.close();
                return true;
            }
        }
        reader.close();
    } catch (IOException e) {
        // error reading mounts
    }

    return false;
}

C++의 네이티브 Root Detection 구현

Java로 구현된 Root Detection은 Java 메서드를 가로채고 반환 값을 대체하는 Xposed 모듈이나 Frida를 통해 쉽게 우회됩니다. JNI를 통한 C++의 네이티브 구현은 훨씬 더 저항력이 있습니다. Java 수준에서 작동하는 동적 분석 도구는 stat, access, popen, dlopen과 같은 네이티브 libc 호출을 볼 수 없기 때문입니다.

cpp
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <vector>

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_checker_RootCheck_nativeCheck(
    JNIEnv* env, jobject instance) {

    std::vector<const char*> paths = {
        "/system/bin/su",
        "/system/xbin/su",
        "/sbin/su",
        "/data/local/su"
    };

    struct stat st;
    for (const char* path : paths) {
        if (stat(path, &st) == 0) {
            return JNI_TRUE;
        }
    }
    return JNI_FALSE;
}

네이티브 검사는 Java API를 사용하지 않으므로 Dalvik/ART 수준에서 작동하는 우회 도구에 보이지 않습니다. 추가 보호를 위해 상수(경로 목록)를 읽기 전용 섹션에 저장하지 말고 간단한 가역 함수를 통해 계산하는 것이 좋습니다. libc의 stat 호출은 Java 래퍼를 우회하여 Linux 커널에 직접 액세스하며 Xposed를 통해 가로챌 수 없습니다.

Root Detection 우회 방법

보안 개발자는 강력한 탐지 시스템을 구축하기 위해 기존 우회 방법을 이해해야 합니다. 각 우회 방법은 적절한 수준에서 대응 조치가 필요합니다.

Magisk Hide 및 Zygisk를 통한 우회

Magisk는 Android 9–14에서 가장 인기 있는 루팅 도구입니다. Magisk Hide는 /proc에서 su의 존재를 숨기고 경로 검사 결과를 위조합니다. Magisk는 커널 수준에서 작동하며 애플리케이션이 확인하기 전에 stat()과 access()를 가로챕니다. 대응 조치: /sbin/.magisk의 존재를 통해 Magisk 자체를 확인하거나 애플리케이션 자체 maps를 읽어 확인 — Magisk는 모든 프로세스에 자체 라이브러리를 주입합니다.

Frida를 통한 우회

Frida는 Ptrace 또는 Dobby를 통해 네이티브 함수를 가로챌 수 있는 동적 계측 도구입니다. Frida는 모든 검사의 반환 값을 대체하여 stat 결과를 ENOENT로 위조합니다. 대응 조치: 메모리 내 명령어의 체크섬을 계산하여 네이티브 함수의 무결성을 검증하고 frida-agent.so 또는 frida-helper의 존재를 위해 /proc/self/maps를 분석하여 Frida를 탐지합니다.

APK 패치를 통한 우회

Java로 구현된 Root Detection은 2~3분 안에 제거할 수 있습니다. APK가 apktool을 통해 디컴파일되고, smali 코드에서 메서드의 반환 값이 false로 변경되며, APK가 재구축되고 서명됩니다. 대응 조치: 중요한 로직을 네이티브 코드로 이동하고 Signature API를 통해 런타임에서 애플리케이션의 디지털 서명을 확인하거나 APK 해시를 서버의 참조와 비교합니다.

신뢰할 수 있는 보호를 위한 권장사항

효과적인 Root Detection은 다계층 아키텍처를 기반으로 구축됩니다. 단일 방법만으로는 충분한 보호를 제공할 수 없습니다. 정적 및 동적 검사, 네이티브 코드, 서버 측 검증의 조합이 최대의 저항력을 제공합니다.

서버 측 검증

클라이언트 측 검사에만 의존하지 마십시오. Root Detection 결과를 일회용 세션 토큰과 함께 서버로 보내십시오. 서버가 기능을 차단하거나 제한할지 결정합니다. 이는 클라이언트 애플리케이션이 수정될 수 있지만 서버가 신뢰할 수 있는 당사자로 남아 있는 API 수준의 공격을 방지합니다.

코드 난독화

Root Detection 코드는 난독화되어야 합니다. 공격자가 jadx에서 su 경로 검사의 명확한 시퀀스를 본다면 우회하는 데 몇 분밖에 걸리지 않습니다. ProGuard 또는 DexGuard를 사용하여 제어 흐름을 난독화하고 문자열을 암호화하십시오. 난독화는 보호 코드 분석 시간을 몇 분에서 몇 시간으로 증가시킵니다.

정기적인 서명 업데이트

검사 대상 경로, 패키지 및 지표 목록은 애플리케이션 릴리스마다 업데이트되어야 합니다. 새로운 루팅 및 우회 도구가 매달 등장합니다. 1년 동안 변경되지 않은 정적 목록은 최신 방법을 탐지할 수 없습니다. 검사를 수행하기 전에 애플리케이션 시작 시 서버에서 최신 서명을 로드하는 것이 좋습니다.

자주 묻는 질문

애플리케이션에 Root Detection이 필요한 이유는?

Root Detection은 Android 샌드박스가 비활성화된 기기에서 애플리케이션이 실행되는 것을 방지합니다. 루팅된 기기에서는 모든 애플리케이션이 다른 앱의 데이터를 읽을 수 있습니다. 은행 및 결제 애플리케이션은 PCI DSS 요구사항 및 OWASP Mobile Security 권장사항에 따라 루팅된 기기에서 작동을 차단해야 합니다.

Magisk Hide는 어떻게 작동하나요?

Magisk Hide는 마운트 네임스페이스 메커니즘을 사용합니다. 제외 목록의 각 프로세스에 대해 Magisk는 su 바이너리가 보이지 않는 격리된 네임스페이스를 생성합니다. 이 네임스페이스의 시스템 호출 stat, access, open은 Magisk 파일을 볼 수 없습니다. /proc/self/maps의 존재를 확인하고 magisk 덤프를 검색하여 Magisk를 탐지할 수 있습니다.

Root Detection을 루트 없이 우회할 수 있나요?

네, 애플리케이션이 코드 무결성을 확인하지 않는 경우 가능합니다. Frida를 통해 Java 검사 메서드를 가로채서 강제로 false를 반환하도록 할 수 있습니다. 대응 조치는 C++의 중요한 로직을 네이티브로 구현하고 DEX 파일 해시를 통한 무결성 검증입니다. 난독화 없이 Java의 모든 Root Detection은 5~10분 안에 우회됩니다.

SafetyNet 및 증명이란 무엇인가요?

SafetyNet(더 이상 사용되지 않음) 및 Play Integrity API는 기기 무결성을 확인하는 Google의 서버 측 검사입니다. 부트로더, 시스템 서명 및 루트 상태 확인이 포함됩니다. Play Integrity API는 SafetyNet의 권장 대체품으로 BASIC, DEVICE 및 STRONG의 세 가지 수준을 제공합니다. 클라이언트 측 Root Detection은 서버 측 증명을 보완합니다.

애플리케이션에서 Root Detection을 테스트하는 방법은?

실제 루팅된 기기(예: Magisk가 설치된 Pixel)에 애플리케이션을 설치합니다. 차단이 작동하는지 확인합니다. 그런 다음 애플리케이션에 대해 Magisk Hide를 통해 루트를 숨기고 테스트를 다시 실행합니다. 심층 테스트를 위해 Frida를 사용하여 대상 메서드를 가로채고 네이티브 보호를 우회할 수 없는지 확인합니다.

요약

  • Root Detection은 은행, 결제 및 기업용 Android 애플리케이션을 위한 필수 보안 구성 요소로, 정적 및 동적 검사의 조합으로 작동
  • 정적 메서드는 표준 경로에서 su 바이너리 검색, 설치된 루트 관리자 확인 및 Build.TAGS 시스템 속성 분석 포함
  • 동적 메서드는 rw 모드에서 /system 마운팅 분석, /proc/mounts 무결성 확인 및 특권 명령 실행 테스트 수행
  • 네이티브 구현은 JNI를 통한 C++로 Java 수준에서 작동하는 Xposed 및 Frida에 검사를 보이지 않게 하여 Dobby 또는 Ptrace를 통한 우회 필요
  • Magisk Hide는 마운트 네임스페이스를 통한 Root Detection 우회의 주요 도구로, magisk 라이브러리의 /proc/self/maps 분석을 통해 탐지 가능
  • 권장 아키텍처: 네이티브 검사 + 코드 난독화 + 서버 측 결과 검증 + 정기적인 서명 업데이트
  • Play Integrity API는 Google의 클라이언트 측 Root Detection을 서버 측 기기 증명으로 보완하여 포괄적인 보호 제공

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기