작성자: 보안왕 김보안

 

1탄(iOS)과 2탄(Android)에서는 두 모바일 플랫폼의 뼈대가 되는 파일 시스템과 보안 아키텍처를 다루었습니다. 아직 읽지 않으신 분들은 아래 링크를 먼저 확인해 주시면 흐름을 이해하는 데 큰 도움이 될 것입니다.

 

[1탄] 취약점 점검 전 알아야 할 iOS 파일 시스템과 보안 아키텍처

작성자: 보안왕 김보안들어가며모바일 앱 취약점 점검을 위해선 최소한 루팅, 탈옥, 후킹과 같은 기본적인 점검 Skill이 필요합니다.기본적인 Skill 이전에 점검 대상에 대한 이해를 먼저 다져놓으

l3g4cy.tistory.com

 

[2탄] 취약점 점검 전 알아야 할 Android 파일 시스템과 보안 아키텍처

작성자: 보안왕 김보안 1탄은 iOS의 파일 시스템과 보안 아키텍처를 다룹니다. 1탄은 아래 링크를 참고해주세요.https://l3g4cy.tistory.com/13 취약점 점검 전 알아야 할 iOS 파일 시스템과 보안 아키텍처

l3g4cy.tistory.com

 


들어가며

지난 포스팅들을 통해 각 OS가 데이터를 어떻게 숨기고 보호하는지 '방어막의 구조'를 이해했습니다. 샌드박스가 존재하고, 하드웨어 레벨(SEP, TEE)에서 암호화 키를 통제한다는 사실을 배웠죠.

그렇다면 진단원(공격자)은 실무에서 이 철옹성 같은 방어막을 어떻게 뚫고 들어가 앱의 취약점을 찾아낼까요?

다짜고짜 인터넷에 돌아다니는 Frida 스크립트를 복사해서 실행했는데 에러가 나면 멍하니 화면만 바라보던 시절이 저에게도 있었습니다. 실무 진단에서 성공적인 공격 벡터를 설계하려면 "디바이스 권한 확보(탈옥/루팅) -> 네트워크 프록시 확보 -> 런타임 메모리 분석 및 조작(Frida)"으로 이어지는 일련의 업무 프로세스와 그 이면의 원리를 유기적으로 연결할 줄 알아야 합니다.

이번 3탄에서는 실제 진단 업무 프로세스를 따라가며, 단말기 제어 명령어부터 인증서 주입 기법, 그리고 실무에서 바로 쓰이는 실전 Frida 후킹 코드까지 낱낱이 파헤쳐 보겠습니다.

목표: 실전 진단 환경 구축부터 탐지 솔루션 우회, 런타임 메모리 분석 및 조작까지의 실무 프로세스를 이해하고 실전 기술을 완벽히 내 것으로 만든다.


1단계: 디바이스 제어 및 진단 환경 구축 (Interactive Shell 확보)

진단의 첫 단추는 샌드박스의 제약을 깨부수고 기기 내부를 자유롭게 들여다볼 수 있는 최상위 권한(root)을 확보하는 것입니다. 단순히 탈옥/루팅 아이콘만 띄우는 것이 아니라, PC와 단말기 간의 자유로운 터미널 세션을 확보하는 것이 실무의 시작입니다.

① iOS: 커널 취약점 패치 및 SSH 터미널 확보

iOS는 Checkra1n, Palera1n 등의 툴을 이용해 부트롬(BootROM) 또는 커널 취약점을 익스플로잇하여 탈옥을 진행합니다. 탈옥/조치가 완료되면 기기 내부에 SSH 서버(OpenSSH)를 구동하여 PC와 CLI로 연결합니다.

  • 실무 팁 (USB 터널링): Wi-Fi를 통한 SSH 연결은 패킷 유실이나 지연이 발생할 수 있어 실무에서는 USB 케이블을 이용한 iproxy 터널링을 애용합니다.
# PC 터미널에서 실행: 22번 포트를 PC의 2222번 포트로 매핑
iproxy 2222 22

# 다른 PC 터미널에서 로컬로 접속 (기본 패스워드: alpine)
ssh root@localhost -p 2222
  • 보안 권고: 진단 기기 연결 후에는 반드시 passwd root 명령어를 통해 기본 패스워드(alpine)를 변경해 둡니다. 진단망 내의 다른 인원이 내 진단 단말기에 무단 접속하는 사고를 막기 위함입니다.

② Android: ADB와 SEAndroid 우회

안드로이드는 루팅(Magisk, APatch 등)을 통해 su 바이너리를 확보합니다. PC에 Android SDK 플랫폼 도구를 설치하고 adb(Android Debug Bridge)를 통해 단말기에 침투합니다.

  • 실무 필수 명령어 세트:
# 연결된 디바이스 목록 확인
adb devices

# 디바이스 쉘 접속 및 루트 권한 상승
adb shell
$ su
# (단말기 화면에서 Magisk 권한 허용 팝업 승인 필요)

# SEAndroid(SELinux) 강제 제어 모드 확인 및 임시 해제
# Enforcing(강제 적용) 상태에서는 루트 권한이라도 차단되는 행위가 존재하므로 Permissive(허용)로 변경
# (실무 진단 시 필수 과정!)
# getenforce 명령어로 현재 상태 확인
# setenforce 0 명령어로 Permissive 전환
root@android:/ # getenforce
Enforcing
root@android:/ # setenforce 0
root@android:/ # getenforce
Permissive

2단계: 네트워크 제어 (Burp Suite Proxy 연동 및 SSL Pinning 우회)

앱이 서버와 주고받는 API 패킷을 분석하는 것은 모바일 진단의 70% 이상을 차지합니다. 하지만 2탄에서 배웠듯이 Android 7.0(API 24) 이상부터는 사용자 추가 인증서를 신뢰하지 않으므로, 일반적인 방식으로는 HTTPS 패킷 복호화가 불가능합니다.

이를 해결하기 위해 실무에서는 시스템 인증서 영역에 강제로 주입합니다.

Burp Suite 인증서를 시스템(System CA)으로 승격시키기

  1. Burp Suite 인증서 내보내기: cacert.der 파일로 저장합니다.
  2. 해시값 추출 및 파일명 변경 (OpenSSL 사용):
    안드로이드는 인증서 파일명이 [Subject_Hash].0 형태여야 인식합니다.
# DER 포맷을 PEM 포맷으로 변환
openssl x509 -inform DER -in cacert.der -out cacert.pem

# 안드로이드가 인식할 옛날 방식(Old) 해시값 추출
openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -n 1
# 출력 예시: 9a5ba575

# 해시값을 파일명으로 설정하여 복사
mv cacert.pem 9a5ba575.0
  1. 안드로이드 시스템 디렉터리(/system)에 강제 주입:
    최신 안드로이드 기기는 /system 영역이 Read-Only로 마운트되어 있어 루팅 상태에서 리마운트(Remount) 작업이 필요합니다. 이 작업을 완료하면, 앱은 내가 수동으로 설치한 Burp Suite 프록시 인증서를 국가 공인 인증 기관에서 발급한 정식 인증서로 인식하여 패킷 통과를 허용하게 됩니다.
# 생성한 인증서 파일을 단말기 임시 폴더로 전송
adb push 9a5ba575.0 /data/local/tmp/

adb shell
su

# 에뮬레이터 또는 구형 단말기의 경우 리마운트 진행
mount -o rw,remount /

# 최신 실제 단말기(Magisk 환경)의 경우 overlayfs 등을 사용하거나
# Magisk Trust User Certs 모듈을 사용하여 주입을 자동화하는 것이 실무에서 수월함
cp /data/local/tmp/9a5ba575.0 /system/etc/security/cacerts/
chmod 644 /system/etc/security/cacerts/9a5ba575.0

3단계: 탐지 솔루션 분석 및 우회 (우회 시나리오 설계)

인증서 주입까지 마쳤는데도 앱이 켜지자마자 꺼지거나 네트워크 에러를 뿜는다면, 두 가지 강력한 보호 메커니즘이 작동하고 있는 것입니다.

  1. 우회 탐지(Anti-Rooting / Anti-Jailbreak): 루팅/탈옥 여부 검사
  2. SSL 피닝(SSL Pinning): 앱 소스코드 내에 특정 서버 인증서의 공개키(Hash)를 박아두고 프록시 인증서를 원천 차단하는 기법

진단원은 이때 분석 대상 앱이 어떤 방식으로 방어하는지 공격 표면(Attack Surface)을 식별하고 우회 시나리오를 설계해야 합니다.

탐지 메커니즘 분석법

구분 주요 탐지 기법 (방어) 실무 진단원의 대응 전략 (공격)
탈옥/루팅 탐지 - 특정 파일 존재 여부 검사 (/system/bin/su, /Applications/Cydia.app) - 환경 변수 및 디렉터리 쓰기 권한 테스트 - 파일/디렉터리 접근 관련 API를 후킹하여 파일이 존재하지 않는 것처럼 조작 (File.exists() 등 우회)
안티 디버깅 - ptrace(PTRACE_TRACEME, ...) 호출하여 다른 디버거(Frida 등)의 attach 차단 - /proc/self/statusTracerPid 값 검사 - ptrace 함수 자체를 후킹하여 아무 일도 일어나지 않은 것처럼 0 리턴 - TracerPid 검사 루틴을 찾아 메모리 변조
SSL Pinning - 시스템 TrustManager를 커스텀하여 지정된 인증서 체인만 허용 - TrustManager 클래스의 검증 메소드를 강제로 패스(return void)시키는 Frida 스크립트 실행

4단계: 런타임 제어 (Frida Hooking 실전 구동 및 스크립트 작성)

이제 대망의 Frida(프리다)를 활용해 타깃 앱의 메모리에 실시간으로 침투해 보겠습니다.

Frida를 이용하면 소스코드를 수정하여 앱을 다시 빌드하지 않고도, 메모리에 로드된 함수를 가로채 흐름을 내 마음대로 바꿀 수 있습니다.

① Frida 서버 구동 (실무 명령어)

우선 단말기 아키텍처(예: arm64)에 맞는 frida-server 바이너리를 다운로드하여 단말기 내부에서 백그라운드로 실행해 주어야 합니다.

# 1. 아키텍처 확인 (Android 기준)
adb shell getprop ro.product.cpu.abi
# 출력 예시: arm64-v8a

# 2. 호환되는 frida-server 파일을 다운로드하여 단말기에 전송
adb push frida-server-16.x.x-android-arm64 /data/local/tmp/frida-server

# 3. 권한 부여 및 백그라운드 실행
adb shell
su
chmod 755 /data/local/tmp/frida-server
/data/local/tmp/frida-server &

② 실전 Frida 스크립트 코드 분석

실무 진단 프로세스에서 가장 많이 쓰이는 안드로이드 루팅 우회iOS 탈옥 우회 스크립트의 뼈대를 직접 분석해 보겠습니다.

[Android] 루팅 탐지 솔루션 무력화 스크립트

오픈소스 루팅 탐지 로직을 무력화하는 자바 인터셉터 예제입니다.

/*
 * 파일명: bypass_root_detection.js
 * 설명: 앱 내의 루팅 검증 함수를 실시간으로 가로채 변조함.
 */

Java.perform(function() {
    console.log("[*] 안드로이드 런타임 제어 시작...");

    try {
        // Target 클래스 로드 (널리 쓰이는 RootBeer 라이브러리 기준 예시)
        var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");

        // 1. isRooted() 메소드가 호출될 때의 동작을 가로챔 (Hooking)
        RootBeer.isRooted.implementation = function() {
            console.log("[+] 루팅 탐지 함수(isRooted) 호출 감지!");

            // 원래 실행 결과는 true(루팅됨)이지만 강제로 false(순정 상태)를 리턴함
            console.log("[->] 루팅 검증 결과를 무조건 'False'로 변조합니다.");
            return false;
        };

        // 2. 다른 세부 탐지 함수들(예: su 바이너리 존재 여부 등)도 개별적으로 우회 설정
        RootBeer.isRootedWithoutBusyBoxCheck.implementation = function() {
            console.log("[+] 세부 탐지 함수 호출 감지 -> 우회 처리");
            return false;
        };

    } catch (err) {
        console.log("[!] Target 클래스를 찾을 수 없습니다: " + err.message);
    }
});
  • 실무 실행 명령어:
# -U: USB 연결 단말기 타깃
# -f: 앱 패키지 명을 지정하여 앱 시작(Spawn) 단계부터 강제 주입
# -l: 주입할 스크립트 파일 경로
frida -U -f com.boan.targetapp -l bypass_root_detection.js

[iOS] Objective-C API 후킹을 통한 파일 검사 우회 스크립트

iOS 앱들이 흔히 사용하는 "특정 경로에 최신 패키지 매니저나 탈옥 흔적이 있는지 검사하는 로직"을 파일 탐색 API 자체를 가로채어 우회하는 스크립트입니다. 최신 환경에선 Cydia 대신 Sileo나 Zebra 같은 매니저를 주로 사용하므로 해당 경로까지 함께 방어해야 합니다.

/*
 * 파일명: bypass_ios_jailbreak.js
 * 설명: iOS NSFileManager 클래스의 파일 존재 여부 확인 메소드를 가로채 
 * 탈옥 관련 경로 검사일 경우 무조건 존재하지 않는다고 거짓 응답을 보냄.
 */

if (ObjC.available) {
    console.log("[*] iOS Objective-C 런타임 분석 중...");

    try {
        // Target 클래스 및 메소드 지정 (NSFileManager의 fileExistsAtPath:)
        var fileManager = ObjC.classes.NSFileManager;
        var hookMethod = fileManager["- fileExistsAtPath:"];

        // 메소드 프록시 설정 (Interceptor.attach)
        Interceptor.attach(hookMethod.implementation, {
            onEnter: function(args) {
                // args[0] = self (객체 자신)
                // args[1] = selector (메소드 이름)
                // args[2] = 첫 번째 파라미터 (검사할 파일 경로)
                this.path = ObjC.Object(args[2]).toString();
            },
            onLeave: function(retval) {
                // 탈옥 및 최신 패키지 매니저 관련 주요 경로 지정
                var jailbreakPaths = [
                    "Sileo",                  // 최신 탈옥 환경 기본 패키지 매니저
                    "Zebra",                  // 차세대 오픈소스 패키지 매니저
                    "Cydia",                  // 구형 패키지 매니저
                    "Jailbreak", 
                    "bin/sh", 
                    "Library/MobileSubstrate", 
                    "etc/apt"
                ];

                for (var i = 0; i < jailbreakPaths.length; i++) {
                    if (this.path.indexOf(jailbreakPaths[i]) !== -1) {
                        console.log("[!] 탈옥 파일 검사 차단됨: " + this.path);

                        // retval은 BOOL 타입이므로, 0(NO/false)으로 강제 치환
                        retval.replace(ptr("0x0")); 
                        break;
                    }
                }
            }
        });
    } catch (err) {
        console.log("[!] iOS Hooking 중 오류 발생: " + err);
    }
} else {
    console.log("[!] Objective-C 런타임이 유효하지 않습니다.");
}
  • 실무 실행 명령어:
frida -U -f com.boan.ios.targetapp -l bypass_ios_jailbreak.js

5단계: 개발자와의 커뮤니케이션 (조치 가이드 제공)

취약점 진단원이 Frida를 이용해 무난히 탐지 솔루션을 우회하고 패킷을 조작하는 데 성공했다면, 이제 가장 중요한 "보고" 단계로 진입해야 합니다.

가령 "루팅 방지 솔루션이 우회되니 우회 안 되게 고치세요"라는 식으로 가이드는 개발자 입장에서 매우 불친절하고 추상적인 가이드입니다. 우리는 동작 원리를 명확히 이해했으니 훨씬 구체적이고 수준 높은 대안을 제시해야 합니다.

💡 신뢰받는 진단원의 조치 가이드 예시

  1. 클라이언트 검증의 한계 인지시키기: "모바일 앱 내부에서 동작하는 모든 검증 코드(Java, Objective-C, Swift)는 Frida 같은 런타임 조작 툴을 통해 변조될 수 있습니다. 따라서 로컬 검증에만 의존해서는 안 됩니다."
  2. 공격 표면 축소 및 난독화 가이드: "단순히 특정 함수(isRooted) 하나에 의존해 차단하지 말고, 비즈니스 로직(예: 로그인, 이체 등)의 중요 길목마다 검증 코드를 복잡하게 분산 배치해 주십시오. 또한 상용 소스코드 및 바이너리 난독화 도구를 적용해 클래스명, 함수명, 로직의 흐름을 알아볼 수 없도록 처리하여 Frida를 통한 공격 지점 탐색을 어렵게 만들어야 합니다."
  3. RASP(Runtime Application Self-Protection) 솔루션 도입 권고: "메모리 변조나 디버거가 연결되는 행위를 실시간으로 감시하고, 탐지 시 프로세스를 즉시 강제 종료시키는 상용 RASP 보안 솔루션 적용을 적극 검토해 주시기 바랍니다."

마치며

오늘 3탄에서는 단순히 기술의 동작 원리를 나열하는 것을 넘어, 실무 진단 환경 구축부터 패킷 확보, Frida 메모리 조작, 그리고 개발자와의 소통 양식까지 포괄하는 실제 진단 프로세스를 심도 있게 정리해 보았습니다.

요약하자면, 모바일 취약점 점검은 단순히 툴을 실행하는 작업이 아닙니다. 대상 시스템의 권한 체계를 깊이 이해하고, 방어자(솔루션)가 쳐놓은 그물을 런타임 분석을 통해 한 올 한 올 풀어내는 치열한 두뇌 싸움입니다.

이 원리를 이해하고 점검에 임한다면, 어떤 까다로운 앱을 만나더라도 막힘없이 우회하고 가치 있는 진단 결과를 만들어낼 수 있을 것입니다.

감사합니다.