Как разархивировать Zimage для Linux

Обновлено: 21.11.2024

В этом сообщении блога объясняется, как извлечь и дизассемблировать образ ядра Linux. В нем рассказывается о скрипте extract-vmlinux, о том, как использовать objdump и как использовать /boot/System.map для поиска функций и других символов.

Извлечение образа ядра Linux (vmlinuz)

Во-первых, вам нужно получить скрипт extract-vmlinux, чтобы вы могли распаковать и извлечь образ ядра Linux.

Вы можете скачать последнюю версию с GitHub:

Маловероятно, что сценарий изменится, но на всякий случай вам следует использовать сценарий extract-vmlinux из того же исходного дерева, что и ваше ядро.

Если вы извлекаете ядро, установленное в вашей операционной системе, вы можете установить скрипт extract-linux с помощью диспетчера пакетов.

В Ubuntu установите linux-headers-$(uname -r):

Вы сможете найти скрипт extract-linux в /usr/src/linux-headers-$(uname -r)/scripts/extract-vmlinux .

В CentOS установите kernel-devel :

Вы сможете найти скрипт extract-linux в /usr/src/kernels/$(uname -r)/scripts/extract-vmlinux .

Использование extract-vmlinux

Теперь вы можете использовать команду extract-vmlinux для распаковки и извлечения образа ядра.

Хорошим первым шагом является создание временного каталога и копирование в него образа ядра:

Теперь запустите скрипт extract-vmlinux, чтобы извлечь образ.

Дизассемблировать ядро ​​Linux с помощью objdump

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

Поиск символов в /boot/System.map

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

К счастью, все символы и их начальные адреса можно найти в файле /boot/System.map-$(uname -r) .

Например, давайте найдем адрес tcp_v4_do_rcv :

Теперь вы можете искать в выводе objdump адрес ffffffff81590df0, чтобы найти дезассемблированную функцию net_ipv4_path:

Заключение

Извлечь ядро ​​Linux относительно просто, если вы знаете, что такое extract-vmlinux и где его найти. Извлечение ядра может быть полезно, если вы хотите проверить комментарии, оставленные авторами кода ядра, или просто хотите посмотреть, как была скомпилирована конкретная функция.

ARM традиционно использует сжатые ядра. Это делается по двум основным причинам:

  • Это экономит место во флэш-памяти или других носителях, содержащих ядро, а память стоит денег. Например, для платформы Gemini, на которой я работаю, несжатое ядро ​​vmlinux занимает 11,8 МБ, а сжатый zImage — всего 4,8 МБ, мы экономим более 50 %
  • Он загружается быстрее, потому что время, необходимое для выполнения распаковки, меньше, чем время, необходимое для передачи несжатого изображения с носителя, например флэш-памяти. Для контроллеров флэш-памяти NAND это может быть легко реализовано.

Это подробное изложение того, как ядро ​​Linux выполняет самораспаковку в устаревших 32-разрядных системах ARM. Все машины под управлением arch/arm/* используют этот метод, если они загружаются со сжатым ядром, а большинство из них используют сжатые ядра.

Загрузчик

Загрузчик, будь то RedBoot, U-Boot или EFI, размещает образ ядра где-то в физической памяти и выполняет его, передавая некоторые параметры в нижних регистрах.

Рассел Кинг определил ABI для загрузки ядра Linux из загрузчика в 2002 году в документе Booting ARM Linux. Загрузчик помещает 0 в регистр r0, идентификатор архитектуры в регистр r1 и указатель на ATAG в регистр r2. ATAG будут содержать расположение и размер физической памяти. Ядро будет размещено где-то в этой памяти. Его можно запустить с любого адреса, если подходит распакованное ядро. Затем загрузчик переходит к ядру в режиме супервизора, при этом все прерывания, MMU и кэши отключены.

В современных ядрах дерева устройств r2 используется как указатель на большой двоичный объект дерева устройств (DTB) в физической памяти. (В этом случае r1 игнорируется.) DTB также может быть добавлен к образу ядра и дополнительно изменен с использованием ATAG из r2. Мы обсудим это подробнее ниже.

Распаковка zImage

Если ядро ​​сжато, выполнение начинается в arch/arm/boot/compressed/head.S в символе start: немного ниже по файлу. (Это не сразу видно.) Он начинается с 8 или 7 инструкций NOP по устаревшим причинам. Он перепрыгивает некоторые магические числа и сохраняет указатель на ATAG. Итак, теперь код распаковки ядра выполняется с физического адреса физической памяти, куда он был загружен.

Затем код декомпрессии находит начало физической памяти.На большинстве современных платформ это делается с помощью выбранного Kconfig кода AUTO_ZRELADDR, что означает логическое И между счетчиком программ и 0xf8000000. Это означает, что ядро ​​легко предполагает, что оно было загружено и выполнено в первой части первого блока физической памяти.

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

Затем к указателю на начало физической памяти добавляется TEXT_OFFSET. Как следует из названия, именно здесь должен располагаться сегмент ядра .text (вывод компилятора). Сегмент .text содержит исполняемый код, так что это фактический начальный адрес ядра после распаковки. TEXT_OFFSET обычно равен 0x8000, поэтому ядро ​​будет расположено в физической памяти на расстоянии 0x8000 байт. Это определено в файле arch/arm/Makefile .

Смещение 0x8000 (32 КБ) является условным, поскольку обычно по адресу 0x00000000 помещаются некоторые данные, относящиеся к неподвижной архитектуре, например векторы прерываний, а многие старые системы размещают ATAG по адресу 0x00000100 . Также должно быть некоторое пространство, потому что, когда ядро, наконец, загрузится, оно вычтет 0x4000 (или 0x5000 для LPAE) из этого адреса и сохранит там исходную таблицу страниц ядра.

Для некоторых конкретных платформ TEXT_OFFSET будет перемещен вниз в памяти, в частности, некоторые платформы Qualcomm будут помещать его в 0x00208000, потому что первые 0x00200000 (2 МБ) физической памяти используются для связи с общей памятью с ЦП модема.

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

Затем ядро ​​устанавливает локальный указатель стека и область malloc(), чтобы мы могли обрабатывать вызовы подпрограмм и выделение небольшой памяти в дальнейшем, выполняя код, написанный на C. Это устанавливается сразу после конца образа ядра.

Сжатое ядро ​​в памяти с прикрепленным DTB.

Затем мы проверяем добавленный большой двоичный объект DTB, активированный символом ARM_APPENDED_DTB. Это DTB, который добавляется к zImage во время сборки, часто с помощью простого cat foo.dtb >> zImage. DTB идентифицируется с помощью магического числа 0xD00DFEED .

Если найден добавленный DTB и установлен CONFIG_ARM_ATAG_DTB_COMPAT, мы сначала расширяем DTB на 50 % и вызываем atagstofdt, который дополняет DTB информацией из ATAG, например блоками памяти. и размеры.

Далее. указатель DTB (то, что было передано как r2 в начале) перезаписывается указателем на добавленный DTB, мы также сохраняем размер DTB и устанавливаем конец образа ядра после DTB, чтобы добавленный DTB (необязательно модифицированный с помощью ATAG) включается в общий размер сжатого ядра. Если найден добавленный DTB, мы также увеличиваем стек и местоположение malloc(), чтобы не уничтожить DTB.

Примечание: если указатель дерева устройств был передан в r2 , а также был предоставлен добавленный DTB, добавленный DTB «выигрывает» и будет использоваться системой. Иногда это можно использовать для переопределения DTB по умолчанию, переданного загрузчиком.

Примечание: если ATAG были переданы в r2 , через этот регистр точно не было передано DTB. Вам почти всегда нужен символ CONFIG_ARM_ATAG_DTB_COMPAT, если вы используете старый загрузчик, который вы не хотите заменять, поскольку ATAG правильно определяет память на старых платформах. Можно определить память в дереве устройств, но чаще всего люди пропускают это и полагаются на загрузчик, чтобы обеспечить это тем или иным способом (загрузчик изменяет DTB) или другим способом (ATAG дополняет добавленный DTB в загрузки).

Распакованное ядро ​​может перекрывать сжатое ядро.

Затем мы проверяем, не перезапишем ли мы сжатое ядро ​​несжатым ядром. Это было бы прискорбно. Если это произойдет, мы проверяем, где в памяти должно заканчиваться несжатое ядро, а затем копируем себя (сжатое ядро) за это место.

Затем код просто делает трюк, чтобы вернуться к перемещенному адресу метки с именем перезапуск: это начало кода для настройки указателя стека и области malloc(), но теперь выполняется по новому физическому адресу. .

Это означает, что он снова настроит стек и область malloc() и будет искать добавленный DTB, и все будет выглядеть так, как будто ядро ​​было загружено в этом месте с самого начала. (С одним отличием: мы уже дополнили DTB ATAG, так что больше этого делать не будем.) На этот раз несжатое ядро ​​не перезапишет сжатое ядро.

Мы перемещаем сжатое ядро ​​вниз, чтобы поместилось распакованное ядро.

Нет проверки на то, закончится ли память, т. е. не скопируем ли мы ядро ​​за пределы физической памяти. Если это произойдет, результат непредсказуем. Это может произойти, если объем памяти составляет 8 МБ или меньше. В следующих случаях: не используйте сжатые ядра.

Сжатое ядро ​​перемещается ниже распакованного ядра.

Теперь мы знаем, что ядро ​​можно распаковать в память, расположенную ниже сжатого образа, и что они не будут конфликтовать при распаковке, и мы выполняем по метке wont_overwrite: .

Мы проверяем, выполняем ли мы по адресу, с которым был связан декомпрессор, и, возможно, изменяем некоторые таблицы указателей. Это для среды выполнения C, выполняющей декомпрессор.

Убеждаемся, что кеши включены. (Там определенно нет места для таблицы страниц.)

Мы очищаем область BSS (поэтому все неинициализированные переменные будут равны 0) также для среды выполнения C.

Затем мы вызываем символ decompress_kernel() в файле boot/compressed/misc.c, который, в свою очередь, вызывает do_decompress(), который вызывает __decompress(), выполняющий распаковку.

Это реализовано в C, и тип распаковки отличается в зависимости от опций Kconfig: тот же самый распаковщик, что и сжатие, выбранное при сборке ядра, будет связан с образом и выполнен из физической памяти. Все архитектуры используют одну и ту же библиотеку декомпрессии. Вызываемая функция __decompress() будет зависеть от того, какой из декомпрессоров в lib/decompress_*.c был связан с образом. Выбор декомпрессора происходит в arch/arm/boot/compressed/decompress.c путем простого включения всего декомпрессора в файл.

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

После распаковки распакованное ядро ​​находится в TEXT_OFFSET, а добавленный DTB (если есть) остается там, где было сжатое ядро.

После распаковки мы вызываем get_inflated_image_size(), чтобы получить размер окончательного распакованного ядра. Затем мы снова очищаем и отключаем кеши.

Затем мы переходим к символу __enter_kernel, который устанавливает r0 , r1 и r2 так, как их оставил бы загрузчик, если только у нас нет присоединенного большого двоичного объекта дерева устройств, и в этом случае r2 теперь указывает на этот DTB. Затем мы устанавливаем программный счетчик на начало ядра, которое будет началом физической памяти плюс TEXT_OFFSET , обычно 0x00008000 в очень традиционной системе, возможно, 0x20008000 в некоторых системах Qualcomm.

Теперь мы находимся в той же точке, как если бы мы загрузили несжатое ядро, файл vmlinux, в память по адресу TEXT_OFFSET , передав (обычно) дерево устройств в r2 .

Запуск ядра: выполнение vmlinux

Несжатое ядро ​​начинает выполняться с символа stext() , начала текстового сегмента. Этот код можно найти в arch/arm/kernel/head.S .

Это тема другого обсуждения. Однако обратите внимание, что код здесь не ищет добавленное дерево устройств! Если необходимо использовать добавленное дерево устройств, вы должны использовать сжатое ядро. То же самое касается расширения любого дерева устройств с помощью ATAG. Это также должно использовать сжатый образ ядра, так как код для этого является частью сборки, которая загружает сжатое ядро.

Распаковка ядра поближе

Давайте подробнее рассмотрим декомпрессию Qualcomm APQ8060.

Сначала вам нужно включить CONFIG_DEBUG_LL , что позволит вам вводить символы в консоль UART без какого-либо вмешательства каких-либо механизмов печати более высокого уровня. Все, что он делает, это предоставляет физический адрес UART и подпрограммы для опроса для выталкивания символов. Он устанавливает DEBUG_UART_PHYS, чтобы ядро ​​знало, где находится физическая область ввода/вывода UART. Убедитесь, что эти определения верны.

Сначала включите параметр Kconfig с именем CONFIG_DEBUG_UNCOMPRESS. Все, что он делает, это печатает короткое сообщение «Uncompressing Linux…» перед распаковкой ядра и «готово, загрузка ядра» после распаковки. Это хороший дымовой тест, показывающий, что CONFIG_DEBUG_LL настроен, DEBUG_UART_PHYS верен и декомпрессия работает, но не более того. Это не обеспечивает низкоуровневой отладки.

Действительную распаковку ядра можно отладить и проверить, включив определение DEBUG в arch/arm/boot/compressed/head.S , это проще всего сделать, пометив -DDEBUG в AFLAGS (флаги ассемблера) для head.S в файле arch/arm/boot/compressed/Makefile вот так:

AFLAGS_head.o += -DTEXT_OFFSET=$(TEXT_OFFSET) -DDEBUG

Затем мы получаем это сообщение при загрузке:

Это означает, что во время загрузки я загрузил ядро ​​по адресу 0x40300000, что привело бы к конфликту с несжатым ядром. Поэтому ядро ​​было скопировано по адресу 0x41801D00, где заканчивается несжатое ядро. Добавив еще несколько отладочных отпечатков, мы видим, что добавленный DTB сначала находится по адресу 0x40DEBA68, а после перемещения ядра вниз он находится по адресу 0x422E56A8, где он остается при загрузке ядра.

zImage: самораспаковывающаяся сжатая версия образа ядра Linux. uImage: файл образа с оболочкой U-Boot (установленной утилитой mkimage), которая включает тип ОС и информацию о загрузчике. Очень распространенной практикой (например, типичный Makefile ядра Linux) является использование файла zImage.

Что такое zImage и bzImage?

zImage ( make zImage ) Это старый формат для небольших ядер (сжатый, менее 512 КБ). При загрузке этот образ загружается в мало памяти (первые 640 КБ ОЗУ). bzImage ( сделать bzImage )

Что такое теги Atag?

Atags — это список информации об определенных аспектах оборудования. Этот список создается загрузчиком перед загрузкой нашего ядра. Загрузчик помещает его по адресу 0x100, а также передает этот адрес ядру через регистр r2.

Что такое образ Rootfs?

Образ rootfs — это просто образ файловой системы, на котором размещена как минимум система инициализации. … Обратите внимание, что какую бы файловую систему вы ни выбрали, ее поддержка должна быть скомпилирована в ядро, чтобы ее можно было смонтировать во время загрузки.

Как получить zImage?

Файл zImage содержит сжатый образ ядра Linux. Если он недоступен в предварительно собранном виде, вы создаете его, скомпилировав исходный код ядра с помощью make zImage. Initramfs, созданный mkinitcpio, представляет собой архив cpio, содержащий файлы исходной файловой системы оперативной памяти, которая используется при запуске.

В чем разница между uImage и zImage?

zImage: самораспаковывающаяся сжатая версия образа ядра Linux. uImage: файл образа с оболочкой U-Boot (установленной утилитой mkimage), которая включает тип ОС и информацию о загрузчике.

Почему это называется вмлинуз?

Двоичный файл ядра оригинальной UNIX, разработанный в Bell Labs, назывался unix. … А поскольку исполняемый файл ядра Linux был преобразован в сжатый файл, а сжатые файлы обычно имеют расширение z или gz в Unix-подобных системах, имя сжатого исполняемого файла ядра стало vmlinuz.

Что такое Initramfs в Linux?

initramfs — это решение, представленное для ядра Linux версии 2.6. … Это означает, что файлы встроенного ПО доступны до загрузки встроенных в ядро ​​драйверов. Инициализация пользовательского пространства вызывается вместо prepare_namespace. Весь поиск корневого устройства и установка md происходит в пользовательском пространстве.

Почему ядро ​​Linux сжато?

Традиционно при создании загрузочного образа ядра ядро ​​также сжимается с помощью gzip или, начиная с Linux 2.6. … В архитектуре SPARC файл vmlinux сжимается с помощью простого gzip, поскольку загрузчик SILO прозрачно распаковывает сжатые gzip-образы.

Как работает Busybox?

Busybox позволяет вам или программам выполнять действия на вашем телефоне с помощью команд Linux (скопированных из Unix). Android — это, по сути, специализированная ОС Linux с машиной, совместимой с Java (Dalvik), для запуска программ.

Первоначально я работал над этим около года назад и представил его PoC||GTFO в феврале 2021 года, как вы можете догадаться по тексту. Если вы хотите пропустить пошаговое руководство по простому подходу к редактированию и сразу перейти к разделу о расширении zImages, перейдите к разделу «Расширение изображения».

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

Механизм этой встроенной файловой системы известен как начальный виртуальный диск 1, который часто принимает форму архива CPIO ( initramfs ). Исходный виртуальный диск встраивается в собственно двоичный файл ядра (vmlinux), который, в свою очередь, сжимается и упаковывается в программу-оболочку (vmlinuz, zImage, bzImage). Оболочка выполняет первоначальную настройку, распаковывает vmlinux, а затем переходит к ней. 2 Сжатый большой двоичный объект vmlinux обычно называют «свинкой» в загрузочном коде Linux.

Несмотря на то, что на этих образах ядра довольно легко запустить extract-vmlinux или binwalk и выпустить поток сценариев оболочки, файлов конфигурации и программ, которые может иметь много причин для изменения, выяснить, как упаковать эти файлы обратно. превратить в изображение, пригодное для исполнения, не так просто.

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

Хотя эта информация предназначена для того, чтобы помочь соседям вносить изменения в проприетарные образы ядра, которые они не могут просто восстановить из исходного кода, я решил использовать 32-разрядную виртуальную сборку OpenWRT для ARM в демонстрационных целях. Пусть тонкий слой запутывания путем сжатия никогда больше не будет мешать вам на пути к проверке концепции!

Настройка

Сначала загрузите образ OpenWRT ARM virt zImage-initramfs:

Мы можем загрузить это в qemu с помощью:

После загрузки нажмите Enter, чтобы активировать консоль. Отображается следующий баннер:

Похоже, это хорошая цель для проверки модификации концепции. Давайте воспользуемся оболочкой для проверки базовой версии ядра Linux:

Большинство интересующих нас файлов можно найти в каталоге arch/arm/boot/compressed.

Извлечение поросенка

Нам нужно извлечь поросенка, прежде чем мы сможем его изменить. Хорошо известный сценарий extract-vmlinux 3 выполняет поиск методом перебора магических байтов часто используемых схем сжатия, запускает для них соответствующую программу распаковки и проверяет, являются ли выходные данные форматом ELF.

Для этого изображения это не удается. Через минуту мы увидим, почему. binwalk идентифицирует сжатые данные XZ:

Очевидно, что по адресу 0x3d10 присутствует магия заголовка потока XZ:

А также магия нижнего колонтитула потока XZ по адресу 0x2bf10c :

binwalk может успешно извлечь сжатый файл vmlinux , но теперь нам нужно знать наверняка, какие именно начальные и конечные смещения используются кодом декомпрессора для выделения поросенка.

Обратите внимание, что в папке arch/arm/boot/compressed есть несколько файлов piggy.*.S. Они включают содержимое свинки в виде двоичного двоичного объекта и хранят его начальное и конечное смещения в глобалах input_data и input_data_end :

На эти глобальные переменные ссылается функция decompress_kernel в файле arch/arm/boot/compressed/misc.c , которая печатает контрольную строку перед вызовом do_decompress с начальным смещением и длиной поросенка в качестве аргументов:

Мы можем использовать перекрестные ссылки на строку «Uncompressing Linux…» в дизассемблированном двоичном файле zImage-initramfs, чтобы найти вызов do_decompress , который укажет нам на значения input_data и input_data_end .

Я загрузил образ как необработанный двоичный файл ARM с прямым порядком байтов 4 в Ghidra, чтобы найти этот вызов при дизассемблировании. Ghidra обнаруживает, что 0x3d10 и 0x2bf114 загружаются из раздела Global Offset Table (GOT) в конце zImage, чтобы настроить первые два регистра для вызова do_decompress .

Эти адреса совпадают с байтами заголовка потока XZ и нижнего колонтитула потока YZ, как показано выше, но есть дополнительное слово, которое идет сразу после нижнего колонтитула потока. Еще немного копаясь в исходном коде, мы подтверждаем, что это представляет несжатый размер данных XZ, и ожидается, что распаковка сломается с помощью обычной команды unxz. 5

Вырежем поросенка:

Используйте параметр --single-stream, чтобы избежать ошибки "Неожиданный конец ввода" при распаковке:

Теперь мы знаем точный размер и расположение поросенка в zImage. Его длина 2864132 ( 0x2bb404 ) байт, он расположен по адресу 0x3d10 - 0x2bf114 .

Модификация

Прямая замена

Для тестовой модификации я изменю байты после предупреждения! строка в баннере. Эти байты отображаются в разделе initramfs распакованного двоичного файла vmlinux, который состоит из несжатого архива CPIO без контрольных сумм. Это достаточно просто для прямого редактирования с помощью шестнадцатеричного редактора:

Если мы попытаемся наивно повторно сжать измененное изображение, оно выйдет значительно больше, чем исходная свинка. Попытка всех предустановок сжатия от -0 до -9 , даже с флагом --extreme, приводит к результату в лучшем случае 2994568 байт. На самом деле, даже если мы просто повторно сожмем оригинальный vmlinux без изменений, в лучшем случае он окажется на 2994540 байт. Это на 130 408 байт больше!

Покопавшись в загрузочных файлах Linux, мы можем найти параметры xz, используемые для сжатия оригинального файла Piggy. Команда находится в xz_wrap.sh:

Давайте попробуем с этими вариантами:

Близко, но оно все еще на 76 байт больше (включая четыре дополнительных байта, необходимых для слова увеличенного размера).Изучив справочную страницу xz и поэкспериментировав с параметрами сжатия, я нашел полезную настройку, которая привела к уменьшению вывода:

Вот описание прекрасной опции на справочной странице xz:

Укажите оптимальную длину совпадения. Как только найдено совпадение хотя бы с хорошими байтами, алгоритм перестает искать возможно лучшие совпадения. Nice может быть 2-273 байта. Более высокие значения, как правило, дают лучшую степень сжатия за счет скорости. Значение по умолчанию зависит от пресета.

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

Сделайте копию образа ядра и обнулите копилку:

Размер несжатого vmlinux остается прежним (9096744 байта), поэтому добавьте его в конец новой свинки как 32-битное целое число с прямым порядком байтов ( 28 ce 8a 00 ). Затем скопируйте новую свинку в область свинки:

Обновите слово input_data_end в GOT ближе к концу изображения (по адресу 0x2bf124). Поросенок теперь заканчивается на 0x2beef0 .

Попробуйте загрузить его в qemu:

Не работает! После быстрой отладки и еще одного взгляда на код становится ясно, что декомпрессор все еще проверяет увеличенный размер поросенка в его исходном местоположении, 0x2bf110. Поскольку мы обнулили исходную область поросенка, декомпрессор будет считывать ноль как увеличенный размер поросенка.

Расположение слова увеличенного размера отображается в блоке адресов в основном ассемблерном коде декомпрессора: 6

Достаточно легко исправить снова с помощью шестнадцатеричного редактора. 0x002bf110 находится по смещению 0x258 в образе, и мы можем обновить его до нового положения слова увеличенного размера, 0x2beef0 - 4 = 0x2beeec. Теперь он загружается:

Оказалось, что мы могли бы все упростить, оставив слово увеличенного размера на его исходном месте. Дополнительные нули в конце данных XZ не мешают распаковщику XZ, и это также избавит нас от необходимости обновлять местоположение слова размера в LC0.

Этот подход работает до тех пор, пока мы можем повторно сжать модифицированный vmlinux до размера, равного или меньше исходного. Некоторые скрипты, такие как repack-zImage.sh, будут выполнять модификации в этом направлении и пытаться оптимизировать сжатие, но не смогут переупаковать initramfs после того, как модификации увеличат его сжатый размер. 7

Расширение изображения

Но что, если никакая настройка параметров компрессора не спасет нас? Что, если мы должны увеличить размер поросенка? Чтобы выяснить, что нужно изменить, если мы переместим конец свинки на более высокий адрес, мы можем использовать макет образа, как описано в скрипте компоновщика arch/arm/boot/compressed/vmlinux.lds.S. 8

GOT в конце таблицы необходимо переместить вверх вместе с адресом раздела bss. Почти все ссылки на расположение этих разделов встроены в объект LC0, показанный выше.

Нам потребуется обновить адреса всего, что расположено после запуска свинки, в том числе:

  • Адреса в объекте LC0
    • __bss_start
    • _end - конец программы (включая bss)
    • _edata — конец изображения
    • надутая локация размером с поросенка
    • _got_start
    • _got_end
    • user_stack_end
    • конец — перезагрузка + 16384 + 1024*1024

    С этого момента я начал использовать скрипт Python для автоматизации редактирования. Объект LC0 легко найти динамически, потому что он начинается со своего собственного адреса (например, для этого zImage слово 0x00000248 находится по смещению 0x248). Мы можем извлечь местоположение GOT из LC0 и использовать его для получения input_data и input_data_end (т. е. начала и конца поросенка). 9 Для каждого значения в LC0 и GOT, превышающего начальное смещение поросенка, мы увеличиваем его на величину, на которую мы увеличиваем размер изображения. Затем мы можем расширить изображение и вставить новую большую свинку поверх исходной.

    Еще один момент, который нужно исправить, — это значение _magic_end в начале изображения: оно соответствует размеру файла zImage. (Это никак не повлияло на загрузку образа qemu.)

    Это уже работает? Неа! Другой сеанс отладки показывает, что аргументы для do_decompress неверны: входное местоположение, длина и указатель функции ошибки равны нулю. Обратите внимание, что все эти значения находятся в GOT.

    Здесь происходит следующее: к нескольким функциям, скомпилированным из кода C ( misc.c и т. д.), добавлены таблицы смещений, которые используются для поиска записей в GOT. Первое смещение в таблице представляет собой смещение ПК относительно самой GOT. Последующие смещения находят в нем определенные записи. Эти записи содержат фиксированный указатель вверх на их глобальный символ.

    input_data и input_data_end — это глобальные переменные, на которые ссылается GOT, поэтому нам придется это исправить. К счастью, нам нужно только исправить базовое смещение GOT для каждой функции.

    Есть несколько простых ограничений, которые мы можем использовать для реализации быстрого поиска и процедуры обновления для этих значений:

    • Эти слова должны существовать только между LC0 и Piggy.
    • Грубое минимальное возможное базовое смещение GOT — это точка, где заканчивается код, до начала GOT: got_start - piggy_start .
    • Грубое максимальное смещение — от начала кода после LC0 до начала GOT: got_start - lc0_end .

    Обновление каждого из этих слов с увеличением размера работает и исправляет расширенное изображение! Вот демонстрация:

    В контексте ПК исходный электронный диск — это более ограниченная файловая система, используемая для промежуточного этапа «раннего пользовательского пространства». Эти образы по-прежнему могут содержать интересные программы, например те, которые используются для получения ключа расшифровки диска от пользователя или доверенного платформенного модуля. ↩

    Если бы binwalk еще не сообщил нам, что изображение имеет порядок следования байтов с прямым порядком байтов, это сделало бы магическое значение порядка следования байтов 0x04030201. Он хранится как 01 02 03 04 ближе к началу изображения, что говорит нам о том, что он хранится с прямым порядком байтов. ↩

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

    Скорее всего вы установили прошивку с чужим ядром. И вам не помогла прошивка с родным доходом. Теперь ваш конфиг отличается от другого.

    Показывает информацию об увеличении, в том числе об аппаратной настройке. Определяет дисплеи, тачскрины, камеры, датчики. Вкладки: .

    Портирование ядра программы об извлечении информации Самый простой способ установить мою программу Device Info HW, включающую адрес .

    Кастомные ядра mt6732 ZTE Blade Q Lux 4G MTS Smart Run 4G ZTE Blade v2 Lite mt6735/6753 Homtom HT7 Pro/HT3 Pro ZTE Blade Z7/X.

    Вы также можете использовать его для boot.img. Сжатый формат: gzip stock_kernel$ path_to/arm-linux-androideabi-objdump -EL -b binary -D -m arm.

    После перехода на ubutnu 18.04 версия meld обновилась с 1.8 до 3.18 Я часто пользовался вводом пути в папку/файл прямо на панели. В но.

    Для запуска мультибута из темы на 4pda на Prestigio PMP5785 от 11.02.2015 Ядро Recovery (Мультибут) Ядро уже встроено в мультибут. .

    Приложение для датчиков тестирования. Пока поддерживаются основные датчики: Акселерометр Датчик освещения Датчик приближения Магнитомет.

    Информация о подключении к Wi-Fi, доступные сети, подключенные устройства. Общее - информация о подключении к Wi-Fi. Сети - список.

    Читайте также: