Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
도구/GitHubGitHub/michalbednarski/organizertransaction
Android SecurityVulnerability AnalysisExploitationMobile App PentestingMobile Security
GitHubmichalbednarski/organizertransaction

OrganizerTransaction

CVE-2021-39749에 대한 PoC로, Android 12L Beta에서 임의의 Activity를 시작할 수 있게 해줍니다.

저장소 보기
37114년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

이것은 CVE-2021-39749에 대한 PoC로, Android 12L Beta에서 다른 앱의 permission 및 exported 설정과 관계없이 해당 앱의 Activity를 시작할 수 있게 해줍니다.

Android 12L에서 TaskFragmentOrganizer 접근은 (의도적으로) 더 이상 MANAGE_ACTIVITY_TASKS 권한을 요구하지 않습니다

여기 제공된 앱을 사용하려면 Hidden API Checks를 비활성화해야 하며, adb shell settings put global hidden_api_policy 1을 통해 수행할 수 있습니다. 이는 보안 경계가 아니며 앱 기반 우회 방법이 알려져 있습니다

이 버그(및 원본 보고서에서 언급된 몇 가지 관련 문제)를 수정하는 커밋은 다음과 같습니다:

  1. startActivityInTaskFragment가 더 이상 Binder.getCallingUid()에 의존하지 않음
  2. ResolverActivity에 이제 가 활성화됨
relinquishTaskIdentity
  • (다른 Activity를 시작하는 데는 필요하지 않지만, 화면에서 위치를 이동하고 투명하게 만들고 탭-재킹이 가능하게 함) TaskFragment의 SurfaceControl이 더 이상 제공되지 않음
  • (여기 코드에는 표시되지 않으며, 원본 보고서에서만 언급된 문제) ActivityRecord#appToken을 TaskFragmentOrganizer로 보낼지 여부를 결정하는 기준이 pid 대신 uid로 변경됨
  • android-12.1.0_r4를 체크아웃하고 처음 3개 커밋(또는 처음 2개)을 되돌릴 수 있습니다. (앱은 여전히 ShutdownActivity를 시작하여 기기를 종료할 수 있지만, "Zoom and set alpha" 체크박스는 작동하지 않습니다)

    (목록의 첫 번째 커밋은 되돌리려고 하면 테스트에서 병합 충돌이 발생하지만, 무시해도 됩니다)

    항상 시스템 uid를 반환하는 Binder.getCallingUid()

    Binder.getCallingUid() 메서드는 현재 처리 중인 Binder 트랜잭션을 보낸 프로세스의 uid를 반환합니다. 해당 uid는 스레드-로컬 변수에 저장됩니다. 트랜잭션을 처리하는 코드는 Binder.clearCallingIdentity()를 호출하여 해당 변수를 자체 프로세스의 uid로 설정할 수 있으며, 이는 트랜잭션 처리 중 이후에 호출되는 메서드에 권한 검사가 Binder 트랜잭션 호출자가 아닌 자체(트랜잭션 처리 코드)에 대해 수행되어야 함을 나타냅니다.

    때때로 Binder.clearCallingIdentity() 이후에 항상 호출되는 Binder.getCallingUid()가 있어 항상 자체 프로세스의 uid를 반환합니다. 때로는 의도적으로 발생합니다. 예를 들어 ActivityTaskManagerService#startDreamActivity에서 (비록 Process.myUid() 또는 Os.getuid()를 사용하는 다소 복잡한 방법이지만)

    저는 스스로 (Soot-기반) 정적 분석 도구를 작성하여 Binder.clearCallingIdentity() 이후에만 발생할 수 있는 Binder.getCallingUid() 호출(및 기타 권한 검사)을 보고합니다. (Soot이 제공하는 Jimple/Shimple IR을 처리하는 사용자 정의 로직이 있지만, Soot로 이를 수행하는 더 나은 방법이 있을 수 있지만 현재는 이 방법을 사용하고 있습니다)

    Android 12L Beta에서 해당 도구는 ActivityStartController#startActivityInTaskFragment에서 하나를 발견했습니다. (참고: Beta 릴리스는 오픈 소스가 아니므로 소스 코드를 사용할 수 없었지만, Shimple은 일반적으로 읽을 수 있으므로 Soot를 Java 디컴파일러로도 사용했습니다)

    startActivityInTaskFragment 호출 방법

    정적 분석 보고서의 일부로 Binder 호출이 시작되는 onTransact() 구현에서 startActivityInTaskFragment까지의 호출 계층 구조를 얻었습니다:

    1. IWindowOrganizerController의 aidl-생성 코드의 onTransact
    2. WindowOrganizerController#applyTransaction (CallerInfo 인수 없음)
    3. WindowOrganizerController#applyTransaction (CallerInfo 인수 포함)
    4. WindowOrganizerController#applyHierarchyOp
    5. ActivityStartController#startActivityInTaskFragment

    applyTransaction에 대한 Binder 호출이 TaskFragmentOrganizer 클래스에 존재한다는 것을 발견했고, 모든 Binder 호출을 직접 수행하는 것보다 더 편리한 래퍼로 사용하기로 결정했습니다. (둘 다 공개 API가 아니므로 어차피 리플렉션을 사용해야 했습니다)

    먼저, 메서드 "2."는 enforceTaskPermission을 호출하는데, Android 12.0에서는 signature-전용 MANAGE_ACTIVITY_TASKS 권한을 확인했으며 우리는 이를 얻을 수 없었지만, Android 12L에서는 규칙이 완화되어 특정 트랜잭션을 권한 없이 수행할 수 있습니다. startActivityInTaskFragment를 수행하는 데 필요한 작업 중 어느 것도 권한이 필요하지 않다는 것이 밝혀졌습니다. (트랜잭션에 TaskFragmentOrganizer가 연결된 경우)

    따라서 우리는 HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT를 수행하려고 합니다. 이를 위해 우리의 TaskFragment가 mLaunchTaskFragments에 등록되어 있어야 합니다. 그렇지 않으면 "Not allowed to operate with invalid fragment token" 예외가 보고됩니다.

    HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT를 통해 이러한 TaskFragment를 등록할 수 있으며, 이는 createTaskFragment()를 호출합니다.

    (PoC 코드에서 이러한 트랜잭션은 SecondActivity에서 전송됩니다: HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT는 initOrganizerAndFragment()에 의해 전송되고 HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT는 startActivityInOrganizer에 의해 전송됩니다)

    따라서 이를 통해 startActivityInTaskFragment를 호출할 수 있으며 여기서 시작된 Activity의 Intent는 시스템 uid에서 온 것으로 간주되지만, 그 자체로는 아무것도 할 수 없다는 것이 밝혀졌습니다: 시스템이 시작한 Activity는 URI 권한을 부여할 수 없으며 다른 앱의 Activity를 시작하려고 하면 canEmbedActivity 검사에 의해 차단됩니다.

    canEmbedActivity 우회

    canEmbedActivity를 다시 살펴보겠습니다: taskFragment.getTask().effectiveUid가 시스템의 uid이거나 시작된 앱의 uid와 일치하면 임베딩이 허용됩니다. effectiveUid가 시스템인 작업에 있어야 합니다.

    또한 createTaskFragment()로 돌아가 보겠습니다: TaskFragment 생성은 rootActivity.getUid() != ownerActivity.getUid()인 경우에만 허용되었습니다. 이는 우리의 Activity가 속한 Task의 백-스택 맨 아래에 있어야 함을 의미합니다.

    새 Task를 시작해야 합니다. (Intent.FLAG_ACTIVITY_NEW_TASK를 통해) 이 Task에는 시스템 uid에 속한 Activity가 있어야 하며(그래서 Task#effectiveUid가 AID_SYSTEM으로 설정됨), 그런 다음 해당 Activity가 (동일한 Task 내에서) 우리의 Activity를 시작하고 finish()를 호출해야 합니다. (그래서 우리의 Activity가 해당 Task의 루트가 되어 createTaskFragment()를 사용할 수 있게 됩니다)

    그러한 Activity 중 하나는 ChooserActivity입니다. Chooser는 일반적으로 "공유" 옵션을 선택한 후 사용자가 사용하려는 앱을 선택하는 데 사용됩니다. ChooserActivity에는 AndroidManifest.xml에 android:relinquishTaskIdentity="true"가 설정되어 있습니다. 이는 다른 Activity를 시작할 때 Task#effectiveUid를 새로 시작된 앱의 uid로 덮어쓴다는 것을 의미합니다.

    (relinquishTaskIdentity는 Task의 첫 번째 앱이 사용하고 시스템 앱인 경우에만 작동하므로, 우리가 직접 relinquishTaskIdentity를 사용하여 시스템 앱을 시작하여 우리 Task의 Task#effectiveUid를 덮어쓸 수는 없습니다)

    우리의 Activity를 시작하고 finish()를 호출할 수 있는 또 다른 Activity는 ResolverActivity입니다. 이는 여러 Activity로 해석되는 암시적 Intent를 시작할 때 사용됩니다. Resolver는 (Chooser와 달리) 선택을 기억하는 옵션을 제공하며, 이를 통해 (휴대폰 사용자로서) 두 가지를 구분할 수 있습니다. ResolverActivity에는 relinquishTaskIdentity가 설정되어 있지 않지만, Resolver는 사용 가능한 옵션을 찾기 위해 자체 Intent를 사용합니다. (Chooser는 Extras에 제공된 Intent를 사용하는 반면) 이는 악용에 문제가 됩니다. Resolver가 선택한 Activity를 시작할 때 사용하는 Intent 플래그는 Resolver를 시작할 때 사용된 것과 동일하기 때문입니다:

    • Intent.FLAG_ACTIVITY_NEW_TASK를 설정하지 않으면 Resolver는 이미 effectiveUid가 영구적으로 설정된 우리의 Task 내에서 시작됩니다.
    • Intent.FLAG_ACTIVITY_NEW_TASK를 설정하면 Resolver는 선택 항목을 또 다른 Task에서 시작하며, 해당 Task의 effectiveUid는 시작된 앱의 uid로 설정됩니다.

    이러한 문제에 대한 해결책은 둘 다 사용하는 것입니다:

    1. 먼저 ChooserActivity를 시작합니다: Intent에 다음을 제공합니다:
      • Intent.FLAG_ACTIVITY_NEW_TASK — Chooser가 새 Task에서 시작되도록 합니다. (다음 Activity 시작까지 시스템의 effectiveUid를 가짐)
      • Intent.EXTRA_INTENT — 어떤 Activity와도 일치하지 않는 Intent로 설정하고 Chooser에 남은 옵션은 Intent.EXTRA_INITIAL_INTENTS에서만 옵니다.
      • Intent.EXTRA_INITIAL_INTENTS — Chooser가 시작하려는 Intent를 포함하는 단일 요소 배열. (옵션이 하나만 있을 때 Chooser와 Resolver 모두 프롬프트를 건너뛰고 유일한 옵션을 즉시 시작하고 finish()를 호출합니다)
    2. 그런 다음 ChooserActivity가 ResolverActivity를 시작합니다:
      • Resolver에는 relinquishTaskIdentity가 설정되어 있지 않으므로 이제 Task#effectiveUid가 시스템으로 설정되고 이 Task에서 시작되는 다음 Activity와 관계없이 유지됩니다.
      • Intent에는 Intent.FLAG_ACTIVITY_NEW_TASK가 없으므로 다음 Activity는 동일한 Task 내에서 시작됩니다.
      • Intent 작업은 비표준으로 설정되어 우리 앱에서 직접 선언한 <intent-filter>와만 일치하므로 Resolver는 즉시 우리 Activity 시작을 진행합니다.
    3. ResolverActivity가 우리 Activity를 시작합니다.
      • 이제 effectiveUid가 AID_SYSTEM인 Task에 있으므로 canEmbedActivity()는 모든 것을 허용합니다.
      • Chooser와 Resolver 모두 finish()를 호출했으므로 우리는 Task의 루트 Activity이며 createTaskFragment()를 사용할 수 있습니다.

    (PoC 앱에서 이러한 단계의 준비는 FirstActivity에서 수행됩니다)

    TaskFragmentOrganizer를 사용한 기타 트릭

    TaskFragmentOrganizer는 onTaskFragmentAppeared 콜백을 통해 SurfaceControl을 받았으며, 해당 SurfaceControl을 사용하여 시작된 Activity를 확대하고 투명하게 만들 수 있으며, 여전히 터치 이벤트를 받고 가려진 것으로 간주되지 않습니다. (따라서 탭-재킹-보호 요소를 여전히 탭할 수 있습니다)

    PoC 앱에서 "Zoom and set alpha" 체크박스를 선택하여 확인할 수 있습니다.

    이는 상단 수정 목록의 커밋 "3."으로 수정되었습니다


    또 다른 점은 TaskFragment 내에서 실행되는 Activity의 ActivityRecord#appToken-s가 TaskFragmentOrganizer 콜백으로 전달된다는 것입니다. 이 목록은 동일한 프로세스 내의 Activity 토큰만 포함하도록 필터링되었지만, 검사는 TaskFragmentOrganizer의 pid와 appToken을 얻을 수 있는 Activity의 pid를 비교하여 수행되었습니다. 실제로 확인하지는 않았지만, 애플리케이션이 TaskFragmentOrganizer를 만들고, 초기에 만드는 데 사용된 프로세스를 종료하고, 해당 pid가 다른 앱의 Activity의 pid로 재사용되어 appToken을 얻을 수 있다고 생각합니다. 다음은 pid 기반 검증을 uid 기반으로 전환하는 커밋입니다. (위 수정 목록의 "4.") (이 커밋은 내 보고서와 독립적으로 이루어진 것 같지만 (보고서 이후에))

    공격자가 Activity의 appToken을 얻으면 onActivityResult() 호출을 주입할 수 있으며 (대상 앱이 직접 startActivityForResult()를 호출하지 않았더라도) activityStopped()를 호출하여 savedInstanceState를 변조할 수 있습니다. (공격자가 대상 애플리케이션이 해당 메서드를 호출하는 것과 경쟁에서 이길 수 있고 추가 호출로 인해 충돌로 인해 상태가 손실되지 않는다고 가정)

    이 경우 가능한지 확인하지는 않았지만, 이전에 CVE-2020-0001 (예, 멋진 번호를 얻었습니다)을 통해 savedInstanceState 변조와 onActivityResult() 주입을 사용하여 시스템 설정 앱을 속여 사용자 상호작용 없이 내 AccessibilityService를 활성화할 수 있었지만, 그것은 다음에 이야기할 내용입니다.

    도구 다운로드