Files
HolodoriCalc/docs/research/android-master-recovery-2026-09-29.md
T
dal4segno 07b98db520
Publish GitHub Pages output / publish (push) Successful in 44s
data: switch to recovered game DB and refresh cards and charts
2026-09-29 22:48:08 +09:00

4.5 KiB

Android 메모리에서 마스터 키 복구 및 DB 복호화

2026-09-29, LDPlayer 14 / Android 14 / 게임 1.2.0, PID 4380에서 수행했다.

결과

  • 게임의 마스터 관리 객체에서 파일별 경로와 키 379개를 복구했다.
  • 원본 암호화 파일 379개, 61,485,056바이트를 PC로 복사했다.
  • 15,011개 페이지 전부의 HMAC-SHA512를 검증하고 AES-256-CBC 복호화에 성공했다.
  • 복호화된 379개 DB 전부를 독립적인 Python 표준 SQLite로 읽기 전용으로 열었다. 모든 PRAGMA integrity_check 결과가 ok였으며, 모든 테이블을 끝까지 읽어 233,312행을 확인했다.
  • Card는 189행, Music은 211행, LangMusic_Kor의 Lang 테이블은 2,167행이다. 행 내부에는 BLOB이 포함되므로 계산기용 필드/JSON 변환을 완료했다는 뜻은 아니다.
  • 게임 프로세스/파일의 수정, 후킹, 재시작, 서버 요청, 계정 인증 정보 사용은 하지 않았다.
  • 계산기 서비스 데이터에는 반영하지 않았다.

확보 방법

  1. root ADB에서 /proc/4380/maps를 읽고 /proc/4380/mem의 필요한 영역을 덤프했다. LDPlayer su 경유 바이너리 출력의 줄바꿈 변환을 피하기 위해 기기 쪽에서 base64로 감싼 뒤 PC에서 디코딩했다.
  2. 이전에 분석한 현재 앱의 IL2CPP 타입 정보로 MasterManager.<>c → declaringType → MasterManagerBase<MasterManager> → static_fields → _instance를 따라갔다.
  3. _downloadedMasterFileInfos의 사전과 DownloadMasterFileInfo 객체들을 읽어 마스터 이름, 저장 경로, 키 byte 배열, 원본 크기를 확보했다. 관련 메모리는 64KiB 단위의 제한된 청크로 저장했으며 전체 프로세스 덤프는 필요하지 않았다.
  4. 키 배열은 67바이트의 SQLCipher x'<64 hex characters>' 형태다. 가운데 64자리 hex는 원시 256비트 키다. 실제 키 값은 이 문서나 Git 추적 파일에 포함하지 않는다.
  5. 원본 파일을 ADB pull로 복사하고, 파일별 복구 키로 각 페이지의 HMAC를 확인한 뒤 복호화했다.

검증된 파일 형식

  • 페이지 크기: 4,096바이트
  • 암호화: AES-256-CBC, 원시 키 32바이트
  • 파일 첫 16바이트: salt
  • 페이지 끝 reserved 영역: 80바이트 (IV 16 + HMAC 64)
  • HMAC: SHA512, 암호문과 IV 뒤에 little-endian 페이지 번호를 포함
  • HMAC 키: 원시 DB 키를 입력으로, salt XOR 0x3A와 PBKDF2-HMAC-SHA512 2회를 사용해 32바이트 파생
  • 첫 페이지의 평문 SQLite 헤더를 복원하고 reserved 영역은 0으로 채운 복사본을 생성했다. 페이지 헤더의 reserved 크기는 유지한다.

설정은 추정으로만 채택한 것이 아니라 전체 페이지 HMAC 일치 및 독립 SQLite 무결성 검사로 검증했다. 암호화와 평문 파일의 SHA-256을 파일별로 남겼다.

로컬 산출물 (Git 제외)

모두 data/inspection/android-master-2026-09-29/ 아래에 있다.

  • master-keys.private.json: 파일별 키, 경로, 마스터 버전 및 객체 주소. 비공개로 유지할 자료.
  • memory/: 키 복구에 사용한 선택적 메모리 덤프. 계정 관련 값이 섞일 수 있으므로 비공개로 유지한다.
  • encrypted-master/: 암호화된 원본 복사본 379개.
  • decrypted-master/: 이름을 붙인 평문 SQLite DB 379개 (Card.sqlite, Music.sqlite 등).
  • decrypt-manifest.json: 암호 검증 페이지 수, 파일 크기 및 양쪽 SHA-256.
  • sqlite-verification.json: DB별 무결성 검사, 테이블 스키마와 행 수.
  • memory.py, master_instance.py, dump_master_keys.py: 메모리 읽기 및 키 복구 스크립트.
  • decrypt-master.mjs, verify_decrypted.py: 로컬 복사본 복호화와 독립 SQLite 검증 도구.

메모리 주소와 타입 레이아웃은 현재 앱 빌드/프로세스 기준이다. 앱을 재시작하거나 업데이트하면 재확인해야 한다. 확보한 키와 같은 버전의 암호화 파일 복사본은 앱 실행 없이도 로컬에서 다시 검증할 수 있다.

수집 기준 확인

작업 전 npm run data:sync를 실행하고 latest.json, latest.md, changes.patch를 모두 확인했다. 영문/한국어/일본어 공식 원격은 기존 9월 26일 기준 커밋과 같고, 모든 콘텐츠 분류에서 변경이 없었다. 이번 로컬 게임 스냅샷의 확보가 HolodoriDB 수집 기준을 갱신하지는 않는다.

참고: SQLCipher 키 API, SQLCipher 설계.