iOS Attack Surface

iOS 에서는 높은 권한을 획득하기 위해 로컬 앱을 설치한 후 데몬의 취약점을 공격하기도 하고, 원격에서 공격하기 위해 Safari의 취약점과 연계하기도 합니다.

로컬 앱 설치와 Safari 접근 모두 사용자의 개입을 적어도 한 번은 요구하게 됩니다.

하지만 SMS 등은 사용자의 개입이 필요하지 않으며 기본적으로 설치된 앱을 사용하기 때문에 여기서 발생하는 취약점은 사용자의 아무런 개입 없이도 트리거가 가능합니다.

이 글은 사용자의 개입이 필요하지 않은 Zero Click Attack Surface 중에서도 iMessage의 취약점에 대해 다룹니다.

Project Zero Monorail을 살펴보면 올해 4월부터 iMessage와 관련된 취약점이 올라오기 시작하여 7월까지 11개 이상의 취약점이 꾸준히 올라왔습니다. iMessage는 무엇이고 어떤 취약점들이 발견되어 왔는지 정리해봤습니다.

iMessage

iMessage는 iOS와 Mac에 기본 설치된 메시지 클라이언트입니다. iMessage는 다양한 메시지 포맷과 extension을 지원합니다. 그 내용은 Samuel Groß가 만든 툴을 이용해서 덤프 가능합니다.

iOS-messaging-tools : https://github.com/googleprojectzero/iOS-messaging-tools/tree/master/iMessage

def on_message(message, data):
    if message['type'] == 'send':
        payload = message['payload']
        if isinstance(payload, dict):
            print("to: {}".format(payload.get('toIdentifier', None)))
            print("from: {}".format(payload.get('fromIdentifier', None)))
            print(payload['message'])
        else:
            print(payload)
    else:
        print(message)

    if data:
        with open('data', 'wb') as f:
            f.write(data)

session = frida.attach("imagent")

code = open('dumpMessages.js', 'r').read()
script = session.create_script(code)
script.on("message", on_message)
script.load()

print("Press Ctrl-C to quit")
sys.stdin.read()

dumpIncomingMessages.py를 실행하여 Mac의 Messages.app으로 수신되는 메시지의 구조를 덤프할 수 있습니다. 이 스크립트는 iMessage의 백그라운드 프로세스인 imagent에 attach하여 수신되는 메시지를 후킹합니다. 덤프된 메시지 내용과 주요 property들은 다음과 같습니다.

to: mailto:TARGET@gmail.com
from: tel:+15556667777
{
    gid = "FAA29682-27A6-498D-8170-CC92F2077441";
    gv = 8;
    p =     (
        "tel:+15556667777",
        "mailto:TARGET@gmail.com"
    );
    pv = 0;
    r = "68DF1E20-9ABB-4413-B86B-02E6E6EB9DCF";
    t = "Hello World";
    v = 1;
}
field desc
t 일반 텍스트 메시지 내용 (Plain Text)
bid 플러그인에 대한 식별자 (Balloon Identifier)
bp 플러그인 데이터 (Plugin Data)
ati 속성 정보 (Attribution Info)
p 대화 참가자 정보 (Participants)

iMessage Serialization / Deserialization

bp 필드와 ati 필드는 NSKeyedArchiver를 사용하여 serialize 되고 NSKeyedUnarchiver를 사용하여 deserialize 됩니다. 특히 bp 필드 같은 경우는 메시지를 클릭하여 메시지 앱을 열었을 때 MobileSMS 프로세스에 의해 deserialize가 수행되기도 하지만, 메시지를 확인하지 않아도 SpringBoard가 notification을 위해서 해당 필드에 대한 deserialize를 수행합니다. 그렇기 때문에 bp 필드를 이용하여 취약점을 트리거 시키는 경우, 메시지를 확인하지 않아도 취약점이 즉시 트리거되는 특징이 있습니다. ati 필드도 사용자 상호 작용 없이 imagent 프로세스에 의해 deserialize 되지만 bp 필드에 비해 내용이 제한적입니다.

NSKeyedArchiver를 사용한 serialization에서는 NSObject 인스턴스를 plist 형태로 인코딩합니다. plist는 class와 property에 대한 딕셔너리로 이루어져 있습니다. deserialize 할 때는 plist의 정보들을 디코딩 하고 NSObject 인스턴스를 초기화하는 initWithCoder: 메소드를 호출합니다. initWithCoder:가 구현된 객체에 대한 인코딩과 디코딩이 가능하지만 NSSecureCoding 이라는 보안 기능이 적용되면 대상이 제한될 수 있습니다.

NSSecureCoding

NSSecureCoding은 iOS 6 에서 도입되었는데 object substitution 공격으로 부터 인코딩 및 디코딩을 보호하기 위한 프로토콜입니다. 객체를 디코딩할 때 디코딩할 객체의 키와 클래스를 미리 지정하고 디코딩된 객체의 클래스가 이와 일치하지 않으면 예외를 발생시켜서 데이터가 변조되었음을 알립니다. NSSecureCoding을 사용하기 위해서는 initWithCoder: 뿐만 아니라 requireSecureCoding:도 구현 되어 있어야 합니다. 또한 디코딩을 허용할 클래스를 제공해야 합니다.

// example without NSSecureCoding
NSNumber *n = [decoder decodeObjectForKey:@"key1"];
// example with NSSecureCoding
[decoder setRequireSecureCoding:YES];
NSNumber *n = [decoder decodeObjectOfclass:[NSNumber class] forKey:@"key1"];

initForReadingFromData:unarchivedObjectOfClassed:fromData:error: 메소드가 NSSecureCoding을 기본적으로 사용하고 있고 initWithData:unarchiveObjectWithData:error, initForReadingWithData: 메소드에서는 기본 설정이 아닙니다.

iMessage 취약점

Blackhat 2019 USA에서 Google Project Zero의 Natashenka가 iMessage에서 취약점을 찾기 위한 다섯 가지 아이디어에 대해 설명했습니다. 다섯 가지 아이디어와 그와 관련된 취약점에 대한 내용을 정리했습니다.

  1. NSSecureCoding 이 활성화되어 있지 않은 deserialize 찾기 (발견되지 않음)
  2. extension 이 serialize 데이터를 처리하는 과정에서 발생할 수 있는 버그 찾기
    • 새로운 기능들이 추가되면서 버그가 발생할 것으로 기대
  3. deserialize를 지원하는 코드에서 발생하는 버그 찾기
    • 메시지 미리 보기 생성 시 SpringBoard에 의해 deserialize 될 수 있는 클래스들의 initWithCoder: 메소드 리뷰
    • NSDictionary, NSString, NSData, NSNumber, NSURL, NSUUID, NSValue와 하위 클래스들 대상
  4. 허용된 클래스의 하위 클래스가 initWithCoder를 구현하지 않은 경우
    • initWithCoder를 오버라이드 하지 않은 경우 상속 규칙에 의해 super class 의 initWithCoder를 사용함
  5. 객체가 cycle을 갖고 있어 deserialize 가 복잡해지는 경우

extension 취약점

CVE-2019-8624 https://bugs.chromium.org/p/project-zero/issues/detail?id=1828

Digital Touch는 그림이나 기타 시각적 요소들을 메시지로 보낼 수 있는 extension 입니다. 데이터로는 포인트 배열, 델타 배열, 컬러 배열이 전송됩니다. [ETTapMessage initWithArchiveData:] 메소드는 포인트 배열이 델타 배열의 두 배 길이인지 확인하지만 컬러 배열에 대해서는 8 바이트보다 긴지만 확인하기 때문에 실제 필요한 컬러 배열 보다 더 짧은 길이의 배열을 serialize 하여 전달할 수 있고, 수신자가 이를 사용할 때 out-of-bound read 가 발생하게 됩니다.

deserialize 코드 취약점

CVE-2019-8661 (Mac Only) https://bugs.chromium.org/p/project-zero/issues/detail?id=1856

NSURL을 deserialize 할 때 발생하는 취약점이며 Mac 에서만 발생합니다. [NSURL initWithCoder:] 메소드는 bookmark 에 대한 디코딩도 제공하고 있습니다. Mac 에서 bookmark는 CarbonCore 프레임 워크의 FSResolveAliasWithMountFlags 함수로 디코딩 되는 alias 파일을 포함할 수 있는데, 이 함수는 ALI_GetUTF8Path를 호출할 수 있고, strcat_chk 에 대한 안전하지 않은 호출을 수행하기 때문에 메모리 손상 문제로 이어질 수 있습니다.

CVE-2019-8646 https://bugs.chromium.org/p/project-zero/issues/detail?id=1858

NSSecureCoding이 활성화 된 경우에도 NSData의 하위 클래스인 _NSDataFileBackedFuture에 대한 deserialize 가 가능합니다. 이 클래스는 로컬 파일을 메모리로 로드할 때 사용됩니다. 여기서는 두 가지 문제가 발생하는데, 첫 번째는 NSData 객체의 length를 NSData 객체가 다루는 바이트 배열의 길이와 다르게 생성할 수 있습니다. 두 번째는 URL이 로컬 파일인지에 대한 검사를 우회할 수 있습니다. Natashenka 가 작성한 페이로드는 다음과 같은 순서로 동작합니다.

1) iMessage가 메시지에 대한 Notification을 표시하기 위해 디코드 수행

2) _NSDataFileBackedFuture 인스턴스는 원격의 URL(공격자의 서버)과 긴 길이의 length로 디코딩 됨

3) _NSDataFileBackedFuture를 바이트로 사용하는 ACZeroingString 인스턴스가 디코딩 됨. 바이트에 접근하면 원격의 URL 에서 데이터를 가져옴. _NSDataFileBackedFuture 클래스는 URL 경로가 시스템에 존재하는지 확인하여 원격의 URL을 차단하지만 스키마를 확인하지 않기 때문에 URL의 경로가 파일 시스템에 존재한다면 원격의 URL 도 가져올 수 있음

// remote url example
http://remote.url//System/Library/ColorSync/Resources/ColorTables.data

4) 원격의 서버는 다음과 같은 URL을 포함하는 버퍼를 응답으로 보냄

http://remote.url//System/Library/ColorSync/Resources/ColorTables.data?val=a

2번 과정에서 _NSDataFileBackedFuture 의 length를 길게 설정했기 때문에 URL 이 포함된 버퍼와 함께 초기화 되지 않은 메모리가 발생함

5) NSURL 클래스는 URL 에 허용되는 문자에 대해 제한하고 있으므로 이를 우회할 수 있는 INDeferredLocalizedString 클래스를 사용하면 4) 번의 초기화 되지 않은 메모리를 포함한 URL 버퍼를 NSURL 로 설정할 수 있음

6) URL 에 접근하면 leak 된 데이터는 URL 의 매개 변수로 하여 원격의 서버로 전송 됨

이 취약점을 활용하면 초기화 되지 않은 메모리 뿐만 아니라 대상 디바이스의 파일도 원격의 서버로 전송할 수도 있습니다.

하위 클래스에서 발생하는 취약점

CVE-2019-8647 https://bugs.chromium.org/p/project-zero/issues/detail?id=1873

_PFArray 는 NSArray 의 하위 클래스입니다. _PFArray 가 deserialize 되면 [NSArray initWithCoder:]를 사용하게 되고 [_PFArray initWithObjects:count:]를 호출합니다. 이 메소드는 NSKeyedUnarchiver 가 제공한 객체로 배열을 초기화 하지만 객체에 대한 소유권을 획득하지는 않습니다. 따라서 NSKeyedUnarchiver 가 해제되면 배열의 객체들도 해제되어 free 된 객체가 _PFArray 배열에 포함되게 됩니다.

복잡한 객체를 사용할 때 발생하는 취약점

NSKeyedArchiver 는 숫자 형태의 reference를 포함하는 plist 파일이기 때문에 객체가 자기 자신을 참조하거나 여러 객체를 포함하는 cycle을 생성할 수 있습니다. 객체를 deserialize 하는 과정은 다음과 같습니다.

if(temp_dict[key])
  return [temp_dict[key] copy];
if(obj_dict[key])
  return [obj_dict[key] copy];
NSObject* a = [NSSomeClass alloc];
temp_dict[key] = a;  //No references!!
NSObject* obj = [a initWithCoder:];
temp_dict[key] = NIL;
obj_dict[key] = obj;
return obj;

각 객체를 디코딩 할 때 먼저 alloc을 사용하여 객체를 할당하고 임시 객체를 위한 dictionary 에 해당 객체를 넣습니다. 그 후 객체에 대한 initWithCoder:를 호출하여 객체를 디코딩하면 객체에 대한 참조를 다시 반환합니다. 이때 cycle을 갖는 객체는 완전히 초기화 되지 않습니다.

CVE-2019-8641 https://bugs.chromium.org/p/project-zero/issues/detail?id=1881

NSDictionary 의 하위 클래스인 NSSharedKeyDictionary 는 dictionary 에서 사용할 key를 미리 정의할 수 있는 클래스입니다. key 는 NSSharedKeySet 인스턴스에 정의되고 이 keyset 은 child keyset도 가질 수 있습니다. 이때 NSSharedKeySet 의 key 로써 새로운 NSSharedKeyDictionary를 설정할 수 있고, 이 dictionary에 대한 NSSharedKeySet으로 이전 keyset과 동일한 keyset을 설정할 수 있습니다. 해당 keyset이 child keyset을 가졌다고 해도 이 단계에서 디코딩되지 않기 때문에 두 번째로 만들어진 dictionary는 child keyset이 없는 keyset을 기반으로 value 배열의 길이를 계산하게 되어 실제 key의 수보다 더 짧은 길이의 배열을 만들게 됩니다. 이 dictionary에 대해 child keyset에 있는 key를 가지고 접근하게 되면 value 값을 읽을 때 out-of-bound 가 발생하게 됩니다.

iMessage 취약점 연구시 주의할 점

Springboard에서 crash가 발생하는 경우, Springboard가 종료되고 다시 실행되면서 crash가 반복적으로 발생하여 UI가 표시되지 않고 핸드폰이 벽돌이 되는 현상이 발생합니다.

복구할 수 있는 방법으로는 다음과 같은 방법들이 있지만 상황(Find my iPhone을 활성화 하지 않았거나 SIM 카드를 사용하지 않는 경우 등)에 따라서는 최신 버전으로 업데이트 해야하는 경우가 생길 수 있으니 주의해야 합니다.

  • 복구 모드로 부팅하고 iTunes를 통해 업데이트 (iOS가 최신버전으로 업데이트 됨)
  • ‘Find my iPhone’을 이용하여 원격으로 디바이스 지우기
  • SIM 카드를 제거하고 Wifi 범위를 벗어난 후 디바이스 지우기

Reference

  1. https://googleprojectzero.blogspot.com/2019/08/the-fully-remote-attack-surface-of.html
  2. http://i.blackhat.com/USA-19/Wednesday/us-19-Silvanovich-Look-No-Hands-The-Remote-Interactionless-Attack-Surface-Of-The-iPhone.pdf