
SnatchBox (CVE-2020-27935)는 macOS 10.15.x 버전까지 영향을 미치는 샌드박스 탈출 취약점 및 익스플로잇입니다.
SnatchBox (CVE-2020-27935)는 macOS 10.15까지, 그리고 macOS 11.0의 초기 베타 버전에 영향을 미치는 샌드박스 이스케이프 취약점입니다. SnatchBox의 가장 중요한 영향은 악의적인 배포자가 필수 macOS App Store 샌드박스를 탈출하여 사용자의 모든 파일에 대한 완전한 접근 권한을 얻을 수 있게 하여, macOS에서 App Store의 보안 모델을 무너뜨린다는 점입니다.
macOS에서는 iOS와 달리 사용자 공간 작업이 자발적으로 샌드박스에 들어가는 사실 자체가 설계상 취약합니다. 잠재적으로 악의적인 작성자는 자신의 메모리 매핑 및 내용을 거의 완전히 제어할 수 있으며, 샌드박스 초기화 전에 실행되는 코드(예: dyld 자체 또는 Objective-C 런타임)가 이 잠재적으로 악의적인 바이너리의 내용을 구문 분석하기 때문에 정교하게 조작된 데이터를 사용하여 샌드박스 초기화 전에 코드 실행을 획득할 수 있습니다. 프로세스가 샌드박스에 적용되기 전에 dyld 코드를 포함한 어떤 코드도 실행하지 않는다면, 이는 문제가 되지 않았을 것입니다. 그 경우 조기 코드 실행이 공격에 아무런 이점을 제공하지 않기 때문입니다. 개념적으로는 Saagar Jha의 샌드박스 우회와 유사하지만, 새로 도입된 완화 조치와 App Store 검증을 우회합니다.
macOS 10.15 이전에는 이 버그의 익스플로잇이 상당히 간단합니다. 샌드박스 초기화 전에 사용되는 클래스(예: OS_xpc_object)에 대한 Objective-C 카테고리를 포함하는 바이너리를 만들고 샌드박스 초기화 전에 사용되는 메서드(가급적이면 상속된 메서드를 사용하여 런타임 경고를 피함)(예: +initialize, 클래스에 처음 접근할 때 암시적으로 호출됨)를 재정의합니다. 카테고리는 샌드박스 초기화 전에 로드되고, OS_xpc_object(또는 다른 적절한 피해자 클래스)에 대한 첫 번째 접근은 카테고리 로드 후, 샌드박스 초기화 전에 이루어지기 때문에, 공격자가 제공한 +initialize 메서드(또는 다른 적절한 피해자 메서드)가 샌드박스 초기화 전에 호출되어 예를 들어 컨테이너 외부의 데이터에 접근할 수 있게 됩니다. 또는 공격자는 _libsecinit_initializer(샌드박스를 초기화하는 함수)에 대한 참조를 nop 유사 함수로 대체하여 (잠재적으로 조건부로) 실행 재개 후에도 샌드박스를 비활성화할 수 있습니다.
macOS 10.15에 사용된 Objective-C 런타임은 앞서 설명한 익스플로잇 기술에 취약하지 않습니다. 그 이유는 didCallDyldNotifyRegister가 설정되기 전에 카테고리가 로드되지 않아, 우리의 +initialize 메서드가 샌드박스 초기화 이후에만 호출되기 때문입니다.
그러나 map_images는 여전히 우리의 바이너리에서 호출되므로, 의도하지 않은 방식으로 런타임 데이터를 변경하여 샌드박스 초기화 전에 코드를 실행할 수 있습니다. 완전하고 주석 처리된 익스플로잇은 main.c에 있지만, 여기서는 기본적인 세부 사항을 설명하겠습니다. 우리는 data 포인터가 libxbc.dylib의 위치를 가리키는 Objective-C 클래스 구조를 조작합니다. 해당 위치는 flags가 비트 31(RW_REALIZED)을 설정하도록 선택되어야 합니다. 그래야 런타임이 이 유효하지 않은 클래스를 실현하려 시도하지 않고 충돌하지 않습니다. 또한 firstSubclass는 재정의하려는 클래스의 isa와 주소를 공유해야 합니다. 다른 (메타) 클래스는 이 유효하지 않은 클래스에서 상속받고 자체 +initialize 메서드를 제공합니다. 우리는 서브클래스를 __objc_nlclslist에 추가하여 런타임이 이 클래스를 실현하도록 합니다.
런타임이 우리의 서브클래스를 실현할 때(이는 샌드박스 초기화 전에 발생함), 유효하지 않은 수퍼클래스와 서브클래스에 대해 addSubclass를 호출하여 피해자의 isa를 우리의 서브클래스를 가리키는 포인터로 대체합니다. 이로 인해 피해자의 모든 메서드가 사실상 우리의 +initialize로 대체됩니다. 적절한 피해자 클래스를 선택했다면 우리의 +initialize 메서드가 샌드박스 초기화 전에 호출됩니다. 그때 다시 _libsecinit_initializer 참조를 nop으로 대체하고(조건부로 또는 무조건), 런타임 변경 사항을 수정하여 나중에 충돌 없이 실행을 재개할 수 있습니다.
제공된 데모는 make를 실행하고, ~/Documents/SecretDocument.txt 파일을 생성한 후, 터미널에서 SnatchBox.app/Contents/MacOS/SnatchBox를 실행하여 빌드할 수 있습니다(com.apple.security.app-sandbox에 의해 번들이 필요하지만, 여전히 명령줄 프로그램입니다). 빌드된 바이너리는 com.apple.security.app-sandbox로 서명되어 일반적으로 ~/Documents/SecretDocument.txt에 대한 접근을 차단하지만(컨테이너 내에 없기 때문에), 여전히 데이터를 읽을 수 있습니다. 런타임 구조 변경으로 인해 이 데모는 수정되지 않은 macOS 10.14 및 이전 버전에서는 작동하지 않지만, 10.15 및 11.0에서는 작동합니다(테스트됨: 10.15.4, 10.15.7 및 11.0 Beta (20A5354i)). 두 가지 익스플로잇 기술을 결합하여 두 런타임 버전을 모두 대상으로 할 수 있지만, 그러한 데모는 제공되지 않습니다.
Example run:
CatalinaVM:SnatchBox lior$ make
mkdir -p SnatchBox.app/Contents/MacOS/
clang -O3 -Wall -framework Foundation main.m -o SnatchBox.app/Contents/MacOS/SnatchBox
cp Info.plist SnatchBox.app/Contents/
codesign --force --sign - SnatchBox.app --entitlements ent.xml
CatalinaVM:SnatchBox lior$ echo "Quack"> ~/Documents/SecretDocument.txt
CatalinaVM:SnatchBox lior$ codesign -d --entitlements :- SnatchBox.app/Contents/MacOS/SnatchBox
Executable=/Volumes/SharedFolders/Home/Projects/SnatchBox/SnatchBox.app/Contents/MacOS/SnatchBox
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
CatalinaVM:SnatchBox lior$ SnatchBox.app/Contents/MacOS/SnatchBox
Found libsecinit_initializer at 0x7fff72309124
Found libSystem.B.dylib at 0x7fff6f0de000
Found __DATA at 0x7fff984eeca0
Replacing libsecinit_initializer reference at 0x7fff984eed48 with a nop
2020-12-18 16:31:48.196 SnatchBox[804:8043] Attempting to read protected file: /Users/lior/Documents/SecretDocument.txt
2020-12-18 16:31:48.197 SnatchBox[804:8043] Escaped sandbox! The contents are: <51756163 6b0a>
앞서 언급했듯이, 이를 통해 App Store 정책에 따라 샌드박스에서 실행되어야 함에도 불구하고 샌드박스에서 실행되지 않는 macOS App Store 애플리케이션을 만들 수 있습니다. 이 취약점은 프레임워크에서도 사용될 수 있으며, 이는 원래 합법적인 App Store 애플리케이션에서 사용될 수 있습니다. 마지막으로, "Xcode Ghost"와 유사한 것과 결합하여 샌드박스 외부에서 실행되는 악성 코드를 App Store 애플리케이션에 대량으로 주입할 수도 있습니다.
Apple은 macOS 11.0 베타 테스트 단계에서 realizeClassWithoutSwift에 malloc_size 호출을 추가하여 이 익스플로잇을 수정했습니다. 이는 클래스가 실현된 것으로 표시된 경우(RW_REALIZED, 우리의 가짜 클래스처럼) 실제로 유효하고 malloc된 데이터 포인터를 올바른 크기(0x20 바이트)로 가지고 있는지 확인합니다. 그렇지 않은 경우 런타임은 realized class 0x100002078 has corrupt data pointer 0x7fff88c00948와 유사한 메시지로 중단됩니다. 이 수정은 iOS, iPadOS, tvOS 및 watchOS에도 적용되었습니다. 직접적인 영향을 받지 않았더라도 말이죠.