
CVE-2018-4248: Чтение за пределами буфера в libxpc во время сериализации строк.
xpc-string-leak — это демонстрационный эксплойт для чтения памяти за пределами границ в libxpc. Этот эксплойт использует уязвимость для чтения памяти кучи за пределами границ из diagnosticd, процесса с корневыми привилегиями без песочницы и правами task_for_pid-allow.
На macOS 10.13.5 и iOS 11.4 функция _xpc_string_deserialize не проверяет, что десериализованная строка имеет правильную длину, перед созданием объекта XPC-строки с помощью _xpc_string_create. Это может привести к чтению кучи за пределами границ в стиле Heartbleed, если XPC-строка затем сериализуется в другое XPC-сообщение.
Вот реализация _xpc_string_deserialize, декомпилированная с помощью IDA:
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 для получения указателя на данные строки, а также сериализованного размера строки, указанного в заголовке строки. Затем _xpc_string_deserialize проверяет, что строка имеет нулевой терминатор в конце указанного размера, но, что критично, не проверяет, что нет нулевого терминатора раньше в данных. И наконец, она создает копию строки в куче и создает объект OS_xpc_string с помощью _xpc_string_create.
Вот декомпилированный код для _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, и устанавливает соответствующие поля в объекте 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);
}
Параметр length объекта OS_xpc_string используется при сериализации без проверки, то есть множество байт читается из кучи в сериализованное сообщение. Если десериализованная строка была короче указанной длины, сообщение будет заполнено данными кучи за пределами границ.
Мы все еще ограничены эксплуатацией XPC-сервисов, которые отражают часть XPC-сообщения обратно клиенту, но это встречается гораздо чаще. Например, на macOS и iOS diagnosticd является многообещающим кандидатом, который также не имеет песочницы, работает с root-правами и имеет привилегии 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 года (январь или февраль), но забыл исследовать ее до мая. Я сообщил о проблеме в Apple 9 мая, ей был присвоен CVE-2018-4248, и она была исправлена в iOS 11.4.1 и macOS 10.13.6 9 июля.
Brandon Azad