본문 바로가기

← 블로그

iOS/바이너리

탈옥 단말에서 Reveal로 상용 앱의 뷰 계층 분석하기

Xcode 뷰 디버거는 내가 빌드한 앱만 볼 수 있습니다. 앱스토어에서 받은 상용 앱의 뷰 계층을 Reveal로 보려면 RevealServer 프레임워크를 앱에 주입해야 하고, 오래돼 동작하지 않던 Reveal2Loader를 최신 Reveal에 맞게 고쳐야 했습니다.

iOS 앱의 화면이 어떤 구조로 짜였는지 알고 싶을 때 뷰 계층만큼 빠른 창이 없습니다. 어떤 뷰가 어디에 얹혀 있고 각 뷰의 클래스가 무엇인지 한눈에 보입니다.

문제는 Xcode의 뷰 디버거가 내가 빌드한 앱에서만 동작한다는 점입니다. 앱스토어에서 받은 상용 앱은 대상이 아닙니다. 남의 앱을 들여다보려면 Reveal이라는 별도 도구와, 그 앱 안에 RevealServer라는 프레임워크를 집어넣는 작업이 필요합니다.

이 글에서는 탈옥 단말에서 상용 앱에 RevealServer를 주입해 뷰 계층을 보는 방법을 다룹니다. 공개된 Reveal2Loader가 최신 Reveal에서는 동작하지 않아, 이를 최신 버전에 맞게 고쳐 쓴 과정까지 정리했습니다.

Reveal은 어떻게 남의 앱을 보여줄까

왜 Xcode 뷰 디버거는 내 앱만 될까?


Xcode 뷰 디버거와 Reveal은 둘 다 앱 안에서 실행되는 코드가 뷰 정보를 바깥으로 넘겨주는 구조입니다. 화면을 그리는 앱 자신이 자기 뷰 트리를 알려줘야 도구가 그림을 그립니다.

Xcode로 앱을 실행하면 디버거가 그 프로세스에 붙어 뷰 정보를 꺼내옵니다. 프로세스에 디버거를 붙이려면 get-task-allow 엔타이틀먼트가 있어야 하는데, 이 값은 개발 빌드에만 들어가고 앱스토어 배포본에는 없습니다. 그래서 내가 빌드한 앱만 됩니다. Reveal에서는 RevealServer 프레임워크가 그 역할을 합니다. 앱이 이 프레임워크를 로드하고 있어야 Reveal 데스크톱 앱이 네트워크로 붙어 뷰 계층을 읽어옵니다.

개발 중인 앱은 RevealServer를 직접 임베드하면 됩니다. 그러나 앱스토어에서 받은 상용 앱에는 이 프레임워크가 들어 있지 않습니다. 그래서 앱 바깥에서 이 프레임워크를 밀어 넣어야 합니다.

상용 앱에 RevealServer 주입하기

앱을 직접 고치지 않고 어떻게 넣을까?


프레임워크를 앱에 넣고 다시 서명해 설치하는 방법도 있습니다. 다만 앱스토어에서 받은 앱은 실행 바이너리가 FairPlay로 암호화돼 있어, 먼저 복호화한 바이너리부터 확보해야 합니다. 그 복호화가 보통 탈옥 단말을 거쳐야 하는 일이라, 상용 앱을 분석하려면 결국 탈옥 단말이 필요합니다. IPA 구조가 낯설다면 IPA 파일이란?을 먼저 보면 좋습니다.

탈옥 단말에서는 앱을 다시 서명할 필요가 없습니다. 앱 파일은 그대로 두고, 앱이 실행되는 순간 런타임에 프레임워크를 끼워 넣으면 됩니다. 탈옥 단말의 트윅이 이 일을 합니다.

Reveal2Loader는 무엇을 하나?


Reveal2Loader는 MobileSubstrate 트윅입니다. 지정한 앱이 실행될 때 RevealServer 프레임워크를 그 앱의 프로세스에 로드하도록 끼워 넣습니다.

트윅이라 탈옥 단말에서만 동작합니다. 단말 설정에서 분석할 앱을 켜면 그 앱에만 RevealServer가 붙습니다. 앱은 자기가 RevealServer를 로드했다고 여기고, Reveal 데스크톱 앱이 여기에 연결합니다.

최신 Reveal에서 안 되는 문제 고치기

왜 공개된 프로젝트 그대로는 안 될까?


공개된 Reveal2Loader(HackingGate, lemon4ex 등의 포크)는 이미 오래전에 나온 프로젝트라, 2024년 시점에는 최신 Reveal과 맞지 않았습니다. 이미 빌드된 deb 패키지 안의 RevealServer가 구버전이기 때문입니다.

Reveal 데스크톱 앱은 연결할 서버와 버전이 맞아야 붙습니다. 구버전 RevealServer가 든 트윅은 최신 Reveal이 거부합니다. 그렇다고 구버전 Reveal을 쓰면 되지 않냐 하면, 체험판이 최신 버전만 제공돼 구버전으로 내려가는 경로도 닫힙니다.

무엇을 바꿔 다시 빌드하나?


트윅에 들어가는 RevealServer를 최신 Reveal의 것으로 교체하고 deb를 다시 빌드하면 됩니다. 절차는 이렇습니다.

  1. theos와 빌드 도구(EasyDev, dpkg)를 설치합니다.
  2. 최신 Reveal 앱에서 RevealServer.xcframework를 꺼내, 단말 아키텍처에 맞는 .framework를 준비합니다.
  3. Reveal2Loader 프로젝트의 Frameworks 자리에 준비한 .framework를 넣습니다.
  4. Xcode Run Script로 빌드해 나온 .dylib를 트윅 패키지의 DynamicLibraries 자리에 넣습니다.
  5. 패키지 디렉터리에서 deb를 빌드하고 단말에 설치합니다.
# 트윅 패키지를 deb 로 빌드
dpkg-deb --build . ./reveal2loader.deb
# 탈옥 단말에 설치
dpkg -i reveal2loader.deb

버전만 맞춰 주면 그다음은 원래 프로젝트가 하던 대로 돌아갑니다. 오래돼 방치된 도구를 최신 환경에 맞게 살려 두는 것이 이 작업의 전부입니다.

설치하고 실제로 뜯어보기


단말에 deb를 설치한 뒤, 설정의 Reveal 항목에서 분석할 앱을 켭니다. 그 앱을 실행하고 Reveal 데스크톱 앱에서 연결하면 뷰 트리와 제약, 각 뷰의 클래스명까지 그대로 보입니다.

Reveal로 앱의 뷰 계층을 연 화면

여기까지 오면 상용 앱의 한 화면이 어떤 뷰로 쌓였는지, 어떤 계층 구조와 커스텀 뷰를 쓰는지 눈으로 확인할 수 있습니다. 정적 분석만으로는 잡기 어려운 화면 구성을 실행 중인 앱에서 바로 읽어내는 것입니다.

지금은 어떤가

이 글은 2024년에 정리한 방법입니다. 도구와 탈옥 환경은 계속 바뀝니다.

  • 그 사이 탈옥 생태계가 rootless 방식(Dopamine, palera1n 등)으로 넘어가면서, 트윅 패키징이 예전 MobileSubstrate 기준과 달라졌습니다. 같은 트윅이라도 지금 환경에 맞게 다시 빌드해야 합니다.
  • A12 이후 arm64e 기기의 rootless 환경에서는 RevealServer가 로드되지 않는 호환성 이슈가 보고되기도 합니다.
  • Reveal은 계속 업데이트되므로, 포크에 든 RevealServer와 데스크톱 Reveal의 버전을 맞추는 일은 그대로 남아 있습니다. 여러 포크가 각각 다른 Reveal 버전을 기준으로 나와 있어, 쓰려는 Reveal 버전에 맞는 것을 골라야 합니다.

바뀌는 것은 포장뿐입니다. RevealServer를 앱에 주입하고 그 버전을 데스크톱 Reveal과 맞춘다는 원리는 같습니다.

어디에 쓰나

앱을 직접 실행해 보며 UI 구조를 파악해야 할 때 유용합니다. 어떤 뷰가 화면을 덮고 있는지, 특정 화면이 네이티브인지 웹뷰인지, 커스텀 뷰의 계층이 어떻게 짜였는지 같은 물음은 뷰 계층을 보면 대부분 바로 풀립니다.

도구 자체는 새로 만든 것이 아니라 이미 있는 Reveal과 Reveal2Loader입니다. 다만 최신 Reveal에 맞게 버전을 맞춰 되살려 두면, 분석이 필요할 때마다 상용 앱의 화면을 열어 볼 수 있는 창이 하나 생깁니다.