
CVE-2018-4248: libxpc 문자열 직렬화 중 경계를 벗어난 읽기
xpc-string-leak은 libxpc의 경계를 벗어난 메모리 읽기 취약점에 대한 개념 증명 익스플로잇입니다.
이 익스플로잇은 취약점을 이용하여 task_for_pid-allow 권한이 있는 샌드박스가 적용되지 않은 루트 프로세스인 diagnosticd에서 힙 메모리를 읽어냅니다.
macOS 10.13.5 및 iOS 11.4에서 _xpc_string_deserialize 함수는 _xpc_string_create로 XPC 문자열 객체를 생성하기 전에 역직렬화된 문자열의 길이가 적절한지 확인하지 않습니다. 이로 인해 XPC 문자열이 다른 XPC 메시지로 직렬화될 때 Heartbleed 스타일의 힙 메모리 읽기 취약점이 발생할 수 있습니다.
IDA로 역컴파일한 _xpc_string_deserialize의 구현은 다음과 같습니다:
OS_xpc_string *__fastcall _xpc_string_deserialize(OS_xpc_serializer *xserializer)
{
OS_xpc_string *xstring; // rbx@1
char *string; // rax@4
char *contents; // [rsp+8h] [rbp-18h]@1
size_t size; // [rsp+10h] [rbp-10h]@1 MAPDST
xstring = 0LL;
contents = 0LL;
size = 0LL;
if ( _xpc_string_get_wire_value(xserializer, (const char **)&contents, &size) )
{
if ( contents[size - 1] || (string = _xpc_try_strdup(contents)) == 0LL )
{
xstring = 0LL;
}
else
{
xstring = _xpc_string_create(string, size - 1);
LOBYTE(xstring->flags) |= 1u;
}
}
return xstring;
}
_xpc_string_deserialize는 먼저 _xpc_string_get_wire_value를 호출하여 문자열 데이터에 대한 포인터와 문자열 헤더에 보고된 직렬화된 크기를 가져옵니다.
그런 다음 보고된 크기의 끝에 null 종결자가 있는지 확인하지만, 그보다 앞서 null 종결자가 없는지는 확인하지 않습니다.
마지막으로 힙에 문자열의 복사본을 만들고 _xpc_string_create를 사용하여 OS_xpc_string 객체를 생성합니다.
다음은 _xpc_string_create의 역컴파일된 코드입니다:
OS_xpc_string *__fastcall _xpc_string_create(const char *string, size_t length)
{
OS_xpc_string *xstring; // rax@1
xstring = (OS_xpc_string *)_xpc_base_create(&OBJC_CLASS___OS_xpc_string, 16LL);
if ( (((_DWORD)length + 4) & 0xFFFFFFFC) + 4 < length )
_xpc_api_misuse("Unreasonably large string");
xstring->wire_length = ((length + 4) & 0xFFFFFFFC) + 4;
xstring->string = string;
xstring->length = length;
return xstring;
}
_xpc_string_create는 _xpc_string_deserialize가 제공한 length 값을 신뢰하고 OS_xpc_string 객체의 적절한 필드를 설정합니다. 이 시점에서 역직렬화된 문자열은 할당된 문자열 데이터보다 큰 length 필드를 가질 수 있습니다.
이론적으로 이 취약점은 xpc_string_get_length를 사용하여 문자열 길이를 가져오는 서비스에서 메모리 손상을 유발하는 데 사용될 수 있지만, 이러한 패턴은 흔하지 않습니다. 덜 강력하지만 더 실용적인 익스플로잇 전략은 문자열이 다시 직렬화되어 클라이언트로 전송되도록 하여 피해 프로세스의 메모리를 Heartbleed 스타일로 엿볼 수 있게 하는 것입니다.
다음은 _xpc_string_serialize의 구현입니다:
void __fastcall _xpc_string_serialize(OS_xpc_string *string, OS_xpc_serializer *serializer)
{
int type; // [rsp+8h] [rbp-18h]@1
int size; // [rsp+Ch] [rbp-14h]@1
type = *((_DWORD *)&OBJC_CLASS___OS_xpc_string + 10);
_xpc_serializer_append(serializer, &type, 4uLL, 1, 0, 0);
size = LODWORD(string->length) + 1;
_xpc_serializer_append(serializer, &size, 4uLL, 1, 0, 0);
_xpc_serializer_append(serializer, string->string, string->length + 1, 1, 0, 0);
}
직렬화 중에 OS_xpc_string의 length 매개변수가 신뢰되므로 힙에서 많은 바이트가 직렬화된 메시지로 읽힙니다. 역직렬화된 문자열이 보고된 길이보다 짧은 경우 메시지에 힙 데이터가 채워집니다.
여전히 XPC 메시지의 일부를 클라이언트에 반영하는 XPC 서비스로 제한되지만, 이는 훨씬 더 일반적입니다. 예를 들어 macOS 및 iOS에서 diagnosticd는 유망한 후보로, 샌드박스가 없고 루트이며 task_for_pid 권한을 가지고 있습니다. Diagnosticd는 진단 메시지(예: os_log에서 생성된 메시지)를 처리하고 이를 수신하려는 클라이언트에 스트리밍하는 역할을 합니다. 자체 진단 스트림을 수신하도록 등록하고 예상보다 짧은 문자열로 진단 메시지를 보내면 diagnosticd의 힙에 있는 일부 데이터 스냅샷을 얻을 수 있으며, 이는 프로세스에서 코드 실행을 달성하는 데 도움이 됩니다.
빌드하려면 make를 실행하세요. 다양한 빌드 옵션은 Makefile 상단을 참조하세요.
명령줄에서 누출 크기를 지정하여 익스플로잇을 실행합니다:
$ ./xpc-string-leak 0x40
0x2000000000000000 0xe00007ff39bf0992
0x00007fff56858570 0x00007fff7ed23d0e
0x0000000000000000 0x0000000000000000
0x00007fff7ed52be2 0x00007fff7ed29ed6
누출 크기는 8의 배수여야 하며 최소 16이어야 합니다.
xpc-string-leak 코드는 퍼블릭 도메인으로 제공됩니다. 이 코드를 참조하거나 사용하는 경우 출처를 표시해 주시기 바랍니다.
이 버그는 2018년 초(1월 또는 2월)에 발견했지만, 5월까지 조사하는 것을 잊었습니다. 5월 9일에 Apple에 문제를 보고했으며, CVE-2018-4248이 할당되었고 7월 9일에 iOS 11.4.1 및 macOS 10.13.6에서 패치되었습니다.
Brandon Azad