MIE에게 사과?
출처: Hacker News
Pardon MIE?
2026년 5월 23일 토요일
Apple의 메모리 무결성 강제(Memory Integrity Enforcement, MIE)는 농담이 아닙니다. 5년 간의 설계, 최신 M5 실리콘, 커널 힙에 대한 하드웨어 메모리 태깅, 커널 핵심 데이터를 위한 하드웨어 잠금 읽기 전용 영역, 그리고 커널 위에 위치해 모든 무단 페이지 테이블 변경을 거부하는 특권 모니터. 이는 소비자 OS가 출시한 가장 진보된 커널 메모리 안전 스택이며, 그럼에도 우회되었습니다. AI 조수와 함께 일하는 3인 팀이 5일 만에 두 개의 버그와 영리한 아이디어 하나로 이를 뚫었습니다. 여기 PhD 없이도 이해할 수 있는 요약이 있습니다.
읽고 싶다면 바로 아래로 건너뛰세요:
메모리 안전이 왜 중요한가
당신이 읽어본 거의 모든 “iPhone이 해킹당했다” 이야기는 같은 근본 원인으로 돌아갑니다: 메모리 버그. 잘못된 대상을 가리키는 포인터. 한 바이트를 초과해 쓰는 버퍼. 해제돼야 할 구조체가 재사용된 경우.
이것은 사용자가 의심스러운 링크를 클릭했기 때문이 아닙니다. 비밀번호가 도난당했기 때문도 아닙니다. 커널이 메모리 조각을 읽고 조금 과하게 신뢰했기 때문입니다.
기자와 활동가들을 대상으로 한 NSO 그룹의 스파이웨어 Pegasus? 메모리 버그. 2020년대 전반에 걸친 타깃 공격에 사용된 WebKit 제로 클릭? 메모리 버그. iPhone 5s부터 X까지 모든 iPhone을 장악한 checkm8 탈옥? 메모리 버그. 몇 년 전 내가 다룬 T2 취약점도 같은 계열입니다.
Google Project Zero는 수년간 이를 집계해 왔습니다. 주요 소프트웨어 제품 전반에 걸친 고위험 취약점의 약 **70%**가 메모리 안전 버그입니다. Microsoft도 동일한 비율을 기록했고, Chrome, Firefox, Apple도 마찬가지였습니다. 업계 전체가 이 문제를 핵심 문제로 인정하고 있습니다.
그렇다면 공격자는 메모리 버그를 가지고 실제로 무엇을 할 수 있을까요?
최악의 경우, 원하는 모든 것을 할 수 있습니다. 커널은 모든 다른 프로그램을 실행하는 프로그램이기 때문입니다. 커널 메모리를 손상시키면 “이 프로세스는 사진을 읽을 수 있다” 혹은 “이 사용자는 uid=501이다” 라는 테이블을 재작성할 수 있습니다. 재부팅 후에도 살아남는 코드를 설치할 수 있고, 기기가 전송한 모든 메시지를 읽을 수 있으며, LED 없이 카메라를 켤 수도 있습니다. 사생활을 보호하려 샀던 전화기가 이제 누군가의 일기장이 된 셈이죠.
이 때문에 Apple, Microsoft, Google 그리고 반도체 업체들은 지난 10년간 하드웨어 수준의 메모리 안전에 막대한 투자를 해왔습니다. 컴파일러 강화, 안전한 할당자, 샌드박싱 등은 모두 도움이 되지만, 결국 새 버그가 계속 등장합니다. 실제로 효과가 있는 방법은 버그가 존재하더라도 악용 불가능하게 만드는 것입니다. 소프트웨어가 올바르다고 믿는 것을 멈추고, 하드웨어가 잘못된 동작을 거부하도록 설계하는 것이죠.
MIE가 바로 그 베팅을 하고 있습니다.
요약: MIE는 무엇을 해야 하는가?
지난 1년간 Apple의 보안 마케팅을 따라왔다면, MIE가 “세대적 도약”이라고 불리는 것을 들어봤을 겁니다. 세 가지 요소가 겹쳐진 구조입니다.
메모리 태깅 (EMTE). 커널이 할당하는 모든 메모리 조각에는 숨겨진 라벨, 즉 태그가 붙습니다. 해당 조각에 접근하기 위해 사용하는 포인터에도 같은 태그가 상위 비트에 삽입되어 주소를 바꾸지 않습니다. 모든 접근 시 CPU는 “포인터의 태그와 메모리의 태그가 일치하는가?”를 검사합니다. 일치하지 않으면 프로세스는 즉시 종료됩니다. Apple 버전은 동기식이라, 검사 시점이 바로 접근 시점이며, 첫 번째 틀린 추측만으로도 즉시 죽습니다.
읽기 전용 영역. 일부 커널 구조는 너무 중요한 나머지 추가 보호를 받습니다. ucred(프로세스의 사용자 ID), Mach 태스크 제어 블록, 샌드박스 테이블, 코드 서명 상태 등이 이에 해당합니다. 이들은 페이지 테이블 자체가 해당 페이지를 쓰기 불가능으로 표시하는 특수 영역에 존재합니다. 링‑0 커널 코드조차 접근할 수 없으며, 하드웨어 MMU가 이를 거부합니다.
단 하나의 진입점. 읽기 전용 영역 페이지를 수정할 수 있는 커널 함수는 정확히 하나뿐입니다: _zalloc_ro_mut. 이 함수는 페이지를 잠시 쓰기 가능하게 만든 뒤 작업을 수행하고 다시 쓰기 불가능하게 되돌립니다. Secure Page Table Monitor라는 고특권 모니터가 모든 페이지 테이블 변화를 감시하고, 다른 어떤 코드가 이를 시도하면 거부합니다. 커널 입장에서는 “오직 _zalloc_ro_mut만이 여기서 쓸 수 있다”는 것이 깨지지 않는 규칙이 됩니다.
이 세 가지를 합치면 MIE가 완성됩니다. 대부분의 커널 메모리는 태그 보호를 받고, 핵심 데이터는 하나의 감사된 라이터를 통해 페이지 테이블 보호를 받습니다. 솔직히 꽤 좋은 설계입니다.
방금 무슨 일이 일어났는가?
2026년 5월 11일, Apple은 macOS Tahoe 26.5를 출시했습니다. 보안 노트(링크) 안에 한 줄짜리 문구가 숨겨져 있었습니다:
Kernel. Apple Silicon이 탑재된 Mac 컴퓨터용. 앱이 예기치 않은 시스템 종료를 일으킬 수 있습니다. Credit: Calif.io in collaboration with Claude and Anthropic Research. CVE-2026-28952.
“예기치 않은 시스템 종료”는 크래시 버그처럼 들리지만, 실제는 그렇지 않습니다. 3일 뒤 Calif는 그들의 공개 보고서를 발표했습니다. 이는 M5와 MIE가 활성화된 macOS에서 최초로 공개된 커널 익스플로잇이며, 비특권 로컬 사용자, 공개 시스템 콜만 사용, 루트 쉘을 획득했습니다. “버그가 전혀 없었다”는 상태에서 5일 만에 작동하는 익스플로잇을 만들었습니다. 전 과정에 Anthropic의 제한된 Mythos Preview 모델을 활용했습니다.
Apple의 패치는 두 명령어로 이루어져 있습니다. 두 개. 그 두 명령어가 전체 이야기를 말해줍니다. 바로 보여드리죠.
arm64 어셈블리를 읽을 수 있다면, 전체 수정 코드는 다음과 같습니다. 아래에서 자세히 풀어보겠습니다.
--- com.apple.kernel @ macOS 26.4.1 :: _zalloc_ro_mut, bounds check
+++ com.apple.kernel @ macOS 26.5 :: _zalloc_ro_mut, bounds check
@@ argument validation @@
- cmp x8, x29 ; stack-frame sanity check (useless)
- b.lo skip_stack_check
- ; 6 instructions of alignment-mask arithmetic
- adds x9, x8, x4 ; target + len,
runs LATE
- b.hs range_check
+ mrs x10, TPIDR_EL1 ; per-CPU pointer
+ adds x9, x8, x4 ; target + len, runs FIRST
+ b.hs per_cpu_check
+ ldr x11, [x10, #0x158] ; per-CPU bound marker
+ ldr x10, [x10, #0xe8] ; per-CPU RO subzone base
+ cmp x8, x11
+ ccmp x9, x11, #0x0, hs ; NEW: target+len vs per-CPU lower
+ ccmp x9, x10, #0x2, hs ; NEW: target+len vs per-CPU upper
+ b.ls panic
세 가지 차이점이 있습니다: 쓸모 없는 스택 오버랩 검사가 사라졌고, 오버플로 검사가 더 앞당겨졌으며, 새로운 per‑CPU 경계 검사가 추가되었습니다. 이미 이해가 되신다면 나머지는 훑어보세요. 아니면 계속 읽어보세요.
버그, 60초 안에
전체 로직은 하나의 커널 함수 _zalloc_ro_mut 안에 들어 있습니다. C 레벨 시그니처는 다음과 같습니다:
void _zalloc_ro_mut(zone_id_t zone, // which RO zone
void *target, // slot to write into
size_t offset, // offset inside the slot
const void *src, // bytes to copy in
size_t len); // how many bytes
즉, “이 RO 영역