모바일 앱
Noir는 모바일 앱이 외부에 노출하는 진입점 — 딥링크와 exported 컴포넌트 — 을 엔드포인트로 추출하고, 이를 처리하는 소스 코드와 연결합니다. 딥링크는 전형적인 모바일 공격 표면입니다. 외부에서 주어진 URL이나 인텐트가 앱 내부로 흘러 들어가 WebView, SQL 쿼리, 다른 컴포넌트에 도달할 수 있기 때문입니다.
Noir가 파싱하는 대상
| 플랫폼 | 소스 파일 | 추출 내용 |
|---|---|---|
| Android | AndroidManifest.xml |
커스텀 URL 스킴 딥링크, exported 인텐트 컴포넌트, exported ContentProvider, 검증된 App Link |
| Android | res/values/strings.xml |
스킴·호스트·경로에 쓰인 @string/ 참조 해석 |
| Android | build.gradle / build.gradle.kts |
패키지·컴포넌트 이름·스킴·호스트에 쓰인 ${applicationId}와 커스텀 manifestPlaceholders 해석. 매니페스트에 package 속성이 없으면 패키지도 여기서 가져옵니다 |
| Android | res/navigation/*.xml |
Jetpack Navigation <deepLink app:uri="..."> 딥링크 — 소속 destination이 처리 컴포넌트가 됩니다 |
| iOS | Info.plist (CFBundleURLTypes를 선언한 모든 *.plist) |
커스텀 URL 스킴 — .xcconfig / project.pbxproj의 $(VAR) / ${VAR} 플레이스홀더도 해석 |
| iOS | *.entitlements |
associated-domains의 applinks: 유니버설 링크와 appclips: App Clip 실행 URL |
| Android | /.well-known/assetlinks.json |
서버 측 App Links 연결 선언(Digital Asset Links) |
| iOS | apple-app-site-association |
서버 측 유니버설 링크 paths / components 패턴 |
앞의 여섯 행은 앱 번들에 선언되는 클라이언트 측 연결이고, 마지막 두 행은 서버 측 — OS가 해당 호스트의 URL에 대해 앱을 열도록 호스트가 게시하는 well-known 파일 — 입니다. 둘 다 동일한 universal-link protocol과 출력 모델을 통해 처리됩니다.
엔드포인트 모델
모바일 진입점은 method = "GET"인 엔드포인트이며, 그 성격은 protocol에 담깁니다.
| protocol | 의미 | 예시 URL |
|---|---|---|
mobile-scheme |
커스텀 URL 스킴 딥링크 | myapp://complex/:id |
android-intent |
인텐트로 도달 가능한 exported 컴포넌트 — data URI 없는 action 필터, 또는 intent-filter가 전혀 없는 경우(명시적 인텐트 전용) | intent://com.example.app/.SyncService |
android-provider |
다른 앱이 content://authority URI로 도달할 수 있는 exported ContentProvider |
content://com.example.app.provider |
universal-link |
검증된 Android App Link / iOS 유니버설 링크, 또는 서버 측 assetlinks.json / apple-app-site-association 패턴 |
https://app.example.com/complex/:id, /buy/* |
plain 출력에서는 SCHEME / INTENT / PROVIDER / UNIVERSAL 프리픽스로 표시됩니다. 처리 컴포넌트, 인텐트 action·category, 호스트, 패키지는 엔드포인트별 metadata 맵에 저장됩니다(JSON / YAML에 직렬화되며, 일반 HTTP 엔드포인트에서는 완전히 생략됩니다).
SCHEME myapp://complex/:id
○ via: .DeepLinkActivity
○ action: android.intent.action.VIEW
○ host: complex
○ package: com.example.myapp
핸들러 코드와의 연결
스캔에 핸들러 소스가 포함되어 있으면, Noir는 각 딥링크 엔드포인트를 이를 처리하는 코드와 연결합니다.
- Android — 매니페스트의 컴포넌트(예:
.DeepLinkActivity)를 해당.kt/.java파일로 해석합니다. 인텐트 처리 메서드(onCreate,onNewIntent,onReceive, …)에서 1-hop callee를 수집하고, 그 안에서 읽는 입력을 파라미터로 만듭니다.uri.getQueryParameter("q")→query파라미터(URL에 baking),get*Extra계열 →extra파라미터. - iOS — 딥링크는 중앙에서 디스패치되므로, Noir는 핸들러를 탐색해 종류별로 연결합니다.
.onOpenURL/application(_:open:)/scene(_:openURLContexts:)는 커스텀 스킴에,userActivity핸들러는 유니버설 링크에 붙입니다.URLQueryItem읽기는query파라미터가 됩니다.
--ai-context를 사용하면 핸들러 본문이 Noir의 source / sink / guard 추론에 입력되어, WebView 로드 같은 싱크로 흘러가는 딥링크가 taint 경로와 함께 드러납니다.
출력 동작
모바일 엔드포인트는 구조화된 인벤토리(JSON, JSONL, YAML, SARIF, Markdown, mermaid, HTML)와 Elasticsearch / webhook export에는 그대로 포함됩니다 — 앱 표면의 일부이기 때문입니다. 반면 HTTP 모양을 가정하는 출력·전송 — cURL, HTTPie, PowerShell, OpenAPI 2.0 / 3.0, 능동 프로빙 / 프록시 전송 — 에서는 제외됩니다. 앱 URL은 보내는 HTTP 요청이 아니라 여는 대상이기 때문입니다.
참고 및 한계
- 핸들러 연결에는 소스 코드가 필요합니다. 매니페스트·plist만 있는 스캔도 엔드포인트는 추출하지만 callee·파라미터·AI 컨텍스트는 붙지 않습니다.
- 바이너리(컴파일된)
Info.plist는 건너뜁니다. 소스 저장소의 XML plist는 파싱합니다. - gradle 플레이스홀더는 매니페스트 위쪽에서 가장 가까운
build.gradle(.kts)를 읽어 해석합니다. 같은 플레이스홀더가 여러 번 선언되면(예:buildTypes오버라이드) 먼저 선언된 쪽 — 관례상defaultConfig— 이 우선하며, 빌드 variant별 값은 모델링하지 않습니다. gradle에 값이 없는 플레이스홀더는 URL에 그대로 남고 엔드포인트에unresolved태그가 붙습니다. - 스킴 없는 Jetpack Navigation URI(런타임에는 http·https 모두 매칭)는
https하나로 추출합니다.{arg}세그먼트는:argpath 파라미터로,?key={arg}쿼리 플레이스홀더는query파라미터로 바뀌고, 끝의.*와일드카드는 리터럴 프리픽스만 남깁니다. - intent-filter가 없는
android:exported="true"컴포넌트는 명시적 인텐트 표면(android-intent, metadata에explicit/exported/component_type포함)으로 보고됩니다. action·category·data URI가 없어도, 컴포넌트 이름을 지정한 명시적 인텐트는 여전히 그 컴포넌트에 도달합니다. exported가 아니거나android:enabled="false"인 필터 없는 컴포넌트는 보고하지 않습니다. 보호용android:permission은 metadata에 기록되지만 표면을 숨기지는 않습니다 —normal/dangerous권한은 다른 앱이 여전히 획득할 수 있기 때문입니다. - exported
<provider>(ContentProvider)는android-provider표면으로 보고됩니다 —android:authorities항목마다content://authority엔드포인트 하나씩. provider 클래스가 핸들러(via)로 연결되므로query/insert/openFile메서드의 callee가 수집되어, raw SQL·파일 싱크가 AI 컨텍스트에 드러납니다.android:readPermission/android:writePermission/android:permission,android:grantUriPermissions(또는<grant-uri-permission>자식), 그리고<path-permission>오버라이드의 존재 여부가 metadata에 실립니다 — 모두 기록만 하고 숨기지 않습니다. 명시적으로 exported된 provider만 보고합니다(최신 SDK에서 provider 기본값은 not-exported).<path-permission>의 경로별 실효 권한은 표시만 하고 해석하지는 않습니다. assetlinks.json은 패키지 연결만 선언하고 경로는 없으므로 앱당/*엔드포인트 하나를 만듭니다.apple-app-site-association의 경로(/buy/*,NOT /private/*)와components는 개별적으로 추출되며, AASA 제외 패턴은excluded태그가 붙습니다.apple-app-site-association은 plain-JSON 형식만 파싱합니다. CMS로 서명된 AASA 파일(구형 앱)은 건너뜁니다.