TL;DR: 탈옥 방법을 찾고 싶으신 분들은 https://github.com/perillamint/RP4JailbreakKit 의 스크립트 셋을 사용하십시오.

들어가며

3월 21 일, 책을 (말도 안 되는) 할인가로 사면 리디 페이퍼 4를 덤으로 끼워주는 급의 “대란” 이벤트가 있었다. 이 이벤트를 통해 리디 페이퍼 4를 덤으로 얻게 되었다.

리디북스 페이퍼 4는, 리디의 전용기여서 기본적으로는 타사 앱 실행이 불가능하다. 하지만 “절대” 는 (특히 회사가 안드로이드 커스터마이징에 있어 락다운을 신경을 그렇게 쓰지 않았다면) 높은 확률로 존재하지 않는 법. 개조를 통해 타사 앱을 사이드로드해 보자.

이미지 얻기

리디북스는 공식적으로 리디 페이퍼에 대한 리커버리 이미지를 제공하지 않는다. 하지만, 공식적으로 제공하지 않는다면 뜯어내면 되는 법, OTA 업데이트를 통해 시스템 이미지를 얻어 보자.

리디 페이퍼는 다음과 같은 OTA URL 포멧을 사용한다.

https://viewer-api.ridibooks.com/app/versions?platform=<platform_name>&serial_number=<device_sn>

이 중, serial number 는 꼭 유효한 시리얼 넘버일 필요는 없으며, 중요한 부분은 platform_name 부분이다. 이를 알아내기 위해, mitmproxy 를 이용해 트래픽을 살펴보자.

(셀 수 없는 올바르지 않은 인증서 경고 속에서 캡쳐된) 리디 서버와의 통신 일부를 통해 리디 페이퍼 4의 플랫폼 이름이 ridipaper_4 임을 알 수 있다.

이를 이횽해 해당 API 를 요청하면 2023-04-22 기준, 다음과 같은 API 응답을 돌려준다

{
  "version_name": "1.0.8",
  "version_code": 2022071901,
  "update_url": "https://viewer-ota.ridicdn.net/ridipaper_4/update-1.0.8-2022071901.zip",
  "description": "&lt;b&gt;[기능 개선]&lt;/b&gt;&lt;br&gt;- 구매목록 필터링 기능 개선 및 사용성 개선&lt;br&gt;- 사용자 책장 내의 연재/시리즈에서 전체 다운로드 기능 추가&lt;br&gt;- 시스템 업데이트 다운로드 후 파일 검사 단계 추가 및 업데이트 안정화&lt;br&gt;&lt;br&gt;&lt;b&gt;[버그 수정]&lt;/b&gt;&lt;br&gt;- 기기 잠금 설정 후 슬립 상태로 전환 시의 오동작 수정&lt;br&gt;- 사용자 책장에서 작품 제거 기능 오동작 수정&lt;br&gt;- 가로모드에서 페이지 넘김 버튼 설정 팝업이 잘리는 문제 수정&lt;br&gt;- 기타 버그 수정"
}

이 응답을 통해서, Android update.zip 이 어디에 존재하는지를 알게 되었다. 해당 upate.zip 을 받아서 열어보자.

update.zip 의 내부 구조. 일반적인 Android 10 의 update.zip 구조를 띄고 있다.

해당 업데이트 패키지는 일반적인 안드로이드 10 update.zip 의 구조를 띄고 있다. 이 업데이트 파일로부터 system, vendor, 그리고 중요한 recovery.img 와 boot.img 를 획득할 수 있다.

이제 (boot 파티션 문제에 한정한다면) 안전하게 되돌아갈 길이 있으니, Magisk 를 설치해서 부팅시켜 보자.

… 부팅이 된다! 이는 해당 부트로더가 (처음 걱정과 다르게) 잠겨 있지 않다는 것을 의미한다. 하지만 이제 다음 문제가 있다. Magisk 는 설치되었지만, Magisk 와 상호 작용 하려면

  • adb 나 기타 방법으로 Magisk.apk 를 설치
  • Magisk App 을 실행 후, Magisk 디렉토리 셋업 진행 후 시스템 재 시동

의 과정이 필요하지만, 리디 페이퍼 4에서는 (부트로더가 잠겨 있지 않음에도) adb (와 파일 관리자, 제대로 된 런쳐, etc…) 의 부재로 인해 해당 과정을 수행하지 못한다. 이를 우회하기 위해서 어떻게든 adb 를 활성화할 방법을 찾아 보자.

ADB 켜기, 어떻게?

adb 를 켜기 위해서는 여러 방법을 생각해볼 수 있다.

sytem.transfer.dat 과 vendor.transfer.dat 을 보면, 거의 system / vendor 이미지의 전체를 담고 있는 것을 확인할 수 있다. 이를 https://github.com/xpirt/sdat2img 툴을 이용해 이미지로 바꾼 뒤 분석해 보자.

system 파티션에는 여러 시스템 앱들이 존재한다. 이 앱들을 apktool 을 이용하여 disassemble 한 뒤, 이 중에 adb 를 켜는 기능이 있는 지점들을 찾아보자.

ᅟSettingsProvider 와 RIDIDeviceManager, ERD850HWTest 에서 adb_enabled 스트링이 발견되었다

SettingsProvider.apk 를 제외하면, RIDIDeviceManager.apk 와 ERD850HWTest.apk 두 가지가 보인다. 이 중, RIDIDeviceManager 의 경우를 살펴보자. RIDIDeviceManager 는 첫 셋업 이후 깨어나서 ro.build.type 이 engineering build 일 경우에 adb 를 활성화하고, user 빌드일 경우, adb 를 비활성화하도록 되어 있다. 더불어, ERD850HWTest.apk 는 팩토리 테스트 기능을 구현하기 위해, user 빌드일 경우 adb 를 활성화하도록 되어 있는 것을 확인할 수 있지만, 해당 앱을 순정 상태에서 호출하기 위해서는 매직 키 콤보가 필요하다. (이 키 콤보는 하단 부록에 추가 기술하였다) 우선은 이 매직 키 콤보를 제외한 방법을 생각해 보자.

이 상황에서 adb 를 켜기 위해, 다음과 같은 방법을 생각해 볼 수 있다.

  1. system.img 를 패치하여 엔지니어링 빌드 상태로 만든다
  2. 첫 셋업이 완료되기 전 Magisk 를 통해 adb 를 임시 활성화한 뒤, LSPosed hook 을 통해 엔지니어링 빌드로 스푸핑한다

하지만 1안의 경우에는 (boot.img 의 Verified Boot 를 비활성화 한다 하더라도) 아직 우리가 풀 super.img 를 획득하지 못하였으며, 풀 플래싱이 아닌 부분 플래싱을 사용하고자 하여도 리커버리의 fastbootd 또한 망가져 있는 상태이기에 아직은 사용이 불가능하다.

2안의 경우,

  1. 첫 셋업이 끝나지 않았을 때
  2. adb wireless 를 prop 설정을 통해 활성화하여
  3. ᅟadb wireless 를 통해 셋업을 진행한 뒤
  4. RIDIDeviceManager의 engineering mode 기능을 통해 지속적으로 ADB 를 활성화 시킨다

의 과정을 계획해 볼 수 있다.

Magisk 패칭

ADB Wireless 를 prop 설정을 통해 활성화하기 위해서는, 우리가 지금 사용할 수 있는 유일한 통로인 boot.img 를 통한 코드 주입을 생각해볼 수 있다. 이를 가장 손쉽게 달성하기 위해서는 magisk 를 패치하여, 각 부트 시퀸스를 진행하는 동안 우리가 원하는 단계에서 원하는 코드를 실행하는 것을 생각해볼 수 있다.

이를 위해, Magisk 의 부트 스테이지 관련 코드가 있는 native/src/core/bootstages.cpp 를 수정하여, prop 을 변경해 보도록 하자.

다음과 같은 패치를 사용하여 부트 완료 후 adb wireless 관련 prop 을 설정하자.

diff --git a/native/src/core/bootstages.cpp b/native/src/core/bootstages.cpp
index 4a7d56264..264df8fb5 100644
--- a/native/src/core/bootstages.cpp
+++ b/native/src/core/bootstages.cpp
@@ -456,8 +456,14 @@ void boot_stage_handler(int client, int code) {
         break;
     case MainRequest::BOOT_COMPLETE:
         close(client);
-        if ((boot_state & FLAG_SAFE_MODE) == 0)
+        if ((boot_state & FLAG_SAFE_MODE) == 0) {
             boot_complete();
+            setprop("ro.build.type", "eng", true);
+            setprop("ro.adb.secure", "0", true);
+            setprop("service.adb.tcp.port", "5555", true);
+            setprop("sys.usb.config", "adb", true);
+            setprop("sys.testmode.adb", "start", true);
+        }
         break;
     default:
         __builtin_unreachable();

이 패치를 적용한 Magisk 를 빌드 후 부팅해 보자.

ADB Wireless 접속

현재 기기의 상태는,

  • 첫 기기 설정이 Wi-Fi 세팅을 제외하면 진행되지 않았고
  • 커스텀 패치된 Magisk 가 적용된 부트 이미지를 통해 부팅되어
  • adb 가 TCP 5555 에서 listening 하고 있는

상태이다. 이제, 호스트에서 기기로 adb connect 후 셸 접속을 해 보자.

$ adb connect xxx.xxx.xxx.xxx:5555
$ adb shell
exynos850:/ $ 

접속에 성공하였다. 이제 빌드한 Magisk.apk 를 설치 후, 루트를 획득해 보자.

$ adb shell
exynos850:/ $ su
exynos850:/ # 

루트 권한 획득에 성공하였다.

보너스 1: 쓸모있게 만들어 보자

리디 페이퍼 4의 안드로이드는 상당히 많이 수정된 안드로이드 10을 사용중이다. 이 커스터마이징에는 대표적으로 다음과 같은 것들이 포함된다:

  • 안드로이드 상태바(Status Bar) 에 홈, 뒤로가기, 밝기 설정, 와이파이, 블루투스, 시스템 설정 버튼
  • 안드로이드 내비게이션 바의 삭제
  • 홈 런쳐 앱의 삭제 (대신 HOME Intent 를 RIDIDeviceManager 가 듣고 있다)
  • 개발자 옵션 접근의 삭제

이러한 제약 사항을 이겨내고, 기기를 타사 앱 구동에 쓸 수 있도록 만들기 위해, SystemUI 에 훅을 걸어, 해당 기능 버튼들을 리매핑하기 위한 LSPosed 모듈을 만들 수 있다.

이 과정 자체는 일반적인 SystemUI.apk 를 디컴파일하고, 훅 포인트들을 찾은 뒤, 훅을 심는 방법이므로, 추가적으로 기술하지는 않고, 이를 구현한 프로젝트의 코드로 대체하고자 한다.

https://github.com/perillamint/RP4Posed/

보너스 2: 매직 키 콤보

리디 시스템 프레임워크에는 팩토리 테스트용 매직 키 콤보가 존재한다. 각 키 콤보는 다음과 같다:

  • KEYCODE_DEMO_APP_1 (301): 전원 버튼과 페이지 다운을 길게 (5초) 누른다.
  • KEYCODE_FEATURED_APP_4 (300): 페이지 다운과 페이지 업을 길게(5초) 누른다
  • KEYCODE_F1: 본체 좌측 퀵 버튼

각 키의 역활은 다음과 같다.

  • KEYCODE_DEMO_APP_1: 팩토리 리셋 실행
  • KEYCODE_FEATUERD_APP_4 이후 KEYCODE_F1: 팩토리 테스트 모드

전원이 꺼진 상태에서의 매직 키는 다음과 같다:

  • 페이지 업과 전원 버튼: Fastboot 진입
  • 페이지 다운과 전원 버튼: Recovery 진입