5P by GN⁺ | ★ favorite | 댓글 1개
  • 여행 일정·숙박 정보·PDF를 보여주는 Travelbound가 사실상 웹에서 받은 콘텐츠를 표시하면서 추적과 여행 광고까지 포함해버려서, 같은 정보를 제공하는 웹페이지로 대체해 봄
  • Android 가상 기기를 루팅하고 HTTP Toolkit으로 트래픽을 가로채, 사용자 이름과 비밀번호를 URL에 결합한 API가 앱의 전체 콘텐츠를 JSON으로 반환한다는 사실을 확인함
  • Ruby 스크립트와 Cron으로 최신 JSON을 주기적으로 가져와 HTML을 생성하고, 광고 데이터는 제외한 채 일정과 PDF 등 필요한 파일만 노출함
  • 웹 버전은 복사·인쇄·저장·북마크·검색이 가능하고 더 다양한 기기에서 작동하며, 원본 앱의 추적 코드와 광고도 제거함
  • 원본 앱은 43MB에서 콘텐츠 다운로드 후 124MB까지 커지지만 웹페이지는 0.05MB이고 선택적 이미지도 35MB에 그쳐, HTML과 HTTP로 전달되는 문서형 콘텐츠에는 웹이 더 적합함

웹페이지로 충분했던 Travelbound

  • 아이가 갈 공연예술학교의 Disneyland 여행 일정, 교통편, 숙박 정보를 확인하려면 Travelbound 앱을 설치해야 했음
  • 앱의 핵심 콘텐츠는 웹으로 전달되는 텍스트·이미지·PDF 링크였음
  • 웹페이지와 구별되는 기능은 사용자에게 불리한 두 가지였음
    • Google Account와 연결된 추적 데이터를 개발자에게 전송함
    • 같은 여행사가 운영하는 다른 여행 상품을 inspirations라는 이름으로 광고함
  • 웹페이지라면 콘텐츠를 복사·인쇄·저장·북마크·검색할 수 있고, 거의 모든 기기에서 이용하며 잠재적으로 접근성도 높일 수 있음

Android 앱 트래픽 가로채기

  • Android Studio의 Virtual Device Manager에서 새 가상 기기를 만들고 adb shell이 작동하는지 확인함
  • rootAVD를 이용해 Android 33 가상 기기를 루팅함
    ./rootAVD.sh system-images/android-33/google_apis_playstore/x86_64/ramdisk.img  
    
  • 루팅은 인증서 고정(Certificate Pinning) 을 사용하는 앱이 중간자 프록시의 자체 서명 TLS 인증서를 신뢰하게 만드는 데 필요했음
    • 루팅하지 않으면 Travelbound 같은 앱이 인증서를 유효하지 않다고 판단해 통신을 거부함
  • 콜드 부팅 후 Magisk를 실행하고, 요청하는 앱에 su 접근을 자동 허용하도록 설정함
    • 자동 허용하지 않으면 HTTP Toolkit이 사용자 응답을 기다리지 않고 비특권 모드로 실행됐음
  • HTTP Toolkit으로 Android 가상 기기의 트래픽을 가로챔
    • 가짜 VPN 공급자를 설치해 기기 트래픽을 프록시로 전달함
    • Android 보안 제약 때문에 설치된 루트 CA 인증서는 수동으로 추가해야 했음
  • Play Store에서 Travelbound를 설치한 뒤, 잡음을 줄이기 위해 해당 앱의 트래픽만 프록시하도록 구성함

API 호출과 데이터 구조

  • 앱은 여행 그룹의 사용자 이름과 비밀번호를 이어 붙여 다음 형식의 API URL을 호출했음
    https://travelbound.api.vamoos.com/api/itineraries/…}  
    
  • 사용자 이름과 비밀번호는 여행 그룹 전체가 공유하는 자격 증명이었음
  • API 응답 JSON에는 앱이 표시하는 모든 콘텐츠가 들어 있었음
    • 여행 일정의 각 구간을 담은 배열
    • inspirations 광고 배열
    • 다른 섹션에서 참조하는 이미지 등의 파일을 담은 상호 참조 배열
  • 앱이 표시할 HTML 코드까지 데이터에 포함돼 있어, 콘텐츠 전달 방식 자체는 웹페이지와 유사했음
  • S3 이미지 URL은 만료 시간이 비교적 짧아 콘텐츠가 바뀌지 않더라도 JSON을 주기적으로 다시 가져와야 했음
    • 다른 방법은 이미지를 로컬에 캐시하는 것이며, 원본 앱은 이 방식을 사용하는 것으로 보였음

JSON을 HTML로 변환

  • Ruby 스크립트를 작성하고 Cron 일정에 따라 최신 JSON을 가져와 HTML 페이지를 생성하도록 구성함
  • 생성 과정에서 데이터 스키마의 overlayRows에 해당하는 inspirations 광고를 완전히 제외함
  • 웹페이지에는 다음 정보만 표시함
    1. 여행 일정 항목
    2. 광고나 일정에서 참조되지 않은 모든 파일
  • 두 번째 목록은 PDF 다운로드 링크를 한곳에 모으는 간단한 방법으로 활용함
  • 생성된 페이지는 기존 여행 그룹에 제공된 것과 같은 비밀번호로 보호함
  • 원본 JSON을 <details> 요소에 넣어, 기존 스크립트가 파악하지 못한 스키마 데이터가 나중에 등장하는지 확인할 수 있게 함

더 작고 유연해진 웹 버전

  • 웹페이지는 원본 앱만큼 화려하지 않지만 크기가 훨씬 작고, 웹의 기본 기능을 별도 구현 없이 제공함
  • 콘텐츠를 복사·인쇄·저장·북마크·검색할 수 있으며 더 다양한 기기에서 접근 가능함
  • 추적 코드와 광고를 제거해 원본 앱에 있던 두 가지 불필요한 기능도 없앰
  • 접근성을 정식으로 감사하지는 않았지만, 원본 앱에는 단순한 웹 버전보다 보조 기술로 사용하기 어려워 보이는 요소가 있었음

크기와 배포 방식 비교

  • 원본 Travelbound 앱은 최초 43MB이며 추가 콘텐츠를 내려받은 뒤에는 124MB까지 커짐
  • 대체 웹페이지는 0.05MB이고, 이미지를 선택적으로 모두 내려받으면 35MB가 추가됨
  • 여행 그룹에는 두 접근 방식을 모두 공유해 사용자가 앱과 웹페이지 중 원하는 방법을 선택할 수 있게 함
  • 일부 작업에는 앱이 적절하지만, 이미 HTML로 작성돼 HTTP로 전달되는 Travelbound 콘텐츠는 앱이 필요한 사례가 아님
  • 앱스토어 배포는 비용과 개발 부담을 늘리며, 이런 문서형 콘텐츠를 앱으로 제한하면 웹에 직접 게시할 때보다 이용 가능한 사람과 기본 기능이 줄어듦

댓글과 토론

Lobste.rs 의견들
  • 본질적으로 웹페이지에 불과한 서비스가 앱 비대화로 이어지는 데 질렸는데, 나만 그런 게 아니라 다행임
    Android에서는 웹페이지 바로가기 만들기가 사용자에게 잘 드러나지 않아 기업이 앱을 선호하는 것도 이해는 된다. 하지만 비밀번호를 문자로 앱 링크와 함께 전달하는 서비스라면 웹 앱이 가장 적합하다. 비밀번호 페이지로 직접 연결하면 끝이다
    /e/OS 앱 스토어에는 Google Maps 웹 버전 같은 항목을 모은 “web apps” 섹션도 있다. PWA 자체는 쓰기 쉽지만 기본 Chrome이나 제조사 Android가 바로가기 생성을 얼마나 적극적으로 안내하고 눈에 띄게 제공하는지는 모르겠다
    현재 방식이 사용자를 위한 것은 분명 아니며, 이를 어떻게 개선해야 할지 고민이 필요함

  • 보통 사용자, 특히 컴퓨터에 익숙하지 않은 사람에게 앱과 웹페이지의 차이는 사실상 바탕 화면이나 홈 화면의 바로가기뿐이다
    웹페이지 바로가기를 곧바로 배포할 수만 있다면 아주 저렴하게 앱을 개발하는 방법이 될 것이고, 아마 대부분의 앱이 그렇게 만들어질 수도 있었을 것임

    • iOS의 Safari에서 웹페이지를 연 뒤 공유 버튼의 “View More”를 거쳐 “Add to Home Screen”을 선택하면 홈 화면에 아이콘을 추가할 수 있다
      이 아이콘을 누르면 새 브라우저 탭이 아니라 일반 앱처럼 독립 실행 창이 열린다. 나는 App Store에 게시하지 않고도 이 방식으로 직접 만든 가족 지출 도구를 배포함
  • 수많은 “앱”은 그냥 웹페이지여도 충분하다. 별 이유도 없이 사소한 기능을 쓰려고 앱부터 설치해야 하는 상황이 너무 많아 답답하다
    로컬 기기 연결을 위해 앱을 쓰는 건 납득할 수 있지만, 정작 앱이 중앙 서버를 거쳐 로컬 기기와 통신하도록 만드는 시스템도 있다. 그러면 앱을 설치해야 할 유일한 명분마저 사라진다
    초기 iPhone은 웹페이지를 앱 플랫폼으로 삼으려 했지만, 결국 지금 같은 엉망인 생태계가 되어버렸다