Сейчас у нас много возможностей и инструментов для исследования различных девайсов — те же приложения для эмуляции вроде QEMU позволяют создавать прекрасную «песочницу» из любого компьютера. К чему это я? Не так давно я решил изучить прошивку ReadyNAS от NetGEAR. Сам девайс — хорошая «рабочая лошадка», меня все устраивало. Но захотелось посмотреть, на чем такие гаджеты работают.
Посмотрел и не зря. Оказалось, что разными моделями ReadyNAS в рамках одной аппаратной архитектуры управляет прошивка на базе Linux — ReadyNAS OS. Причем она доступна для свободного скачивания на сайте производителя. Как по мне — отличная возможность ее изучить и проверить, можно ли запустить такую ОС через открытую систему эмуляции и виртуализации QEMU. Погнали!

Все есть файл
Для начала нужен архив с прошивкой. Он доступен бесплатно и без регистрации на сайте производителя. На момент написания этого текста актуальная версия прошивки — 6.10.8. При скачивании мы получаем обычный ZIP-архив с именем ReadyNASOS-6.10.8-x86_64.zip размером 90,4 Мб.
Распаковываем:
$ unzip ReadyNASOS-6.10.8-x86_64.zip
Смотрим, что получилось:
$ ls -lha
-rw-rw-r-- 1 user user 2,2K сен 27 12:38 ReadyNASOS-6.10.8_Release_Notes.html
-rw-rw-r-- 1 user user 87M сен 27 12:37 ReadyNASOS-6.10.8-x86_64.img
Внутри сама прошивка в формате img и Release Notes. Беглый взор с помощью fdisk — пока что просто похоже на образ диска:
$ fdisk -l ReadyNASOS-6.10.8-x86_64.img
Диск ReadyNASOS-6.10.8-x86_64.img: 86,59 MiB, 90793984 байт, 177332 секторов
Единицы: секторов по 1 * 512 = 512 байт
Размер сектора (логический/физический): 512 байт / 512 байт
Размер I/O (минимальный/оптимальный): 512 байт / 512 байт
Но если бы все было так просто. Имеет смысл заглянуть внутрь при помощи Binwalk:
$ binwalk ReadyNASOS-6.10.8-x86_64.img
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
16384 0x4000 POSIX tar archive (GNU), owner user name: "5"
Вот и первая подсказка. Наш образ — это tar-архив, а не готовый к загрузке диск. Его предстоит распаковать и посмотреть, что лежит внутри. Начинается он по смещению 0x4000, поэтому отрезаем начальный кусок файла, а все остальное складываем в payload.tar:
$ dd if=ReadyNASOS-6.10.8-x86_64.img of=payload.tar bs=1 skip=16384 status=none
Процесс не очень быстрый, так что можно успеть налить себе чашку кофе. Полученный архив просматриваем:
$ tar tvf payload.tar
-rw-r--r-- root/root 128 2022-09-06 20:13 csums.md5
-rw-r--r-- root/root 4592051 2022-09-06 20:13 initrd.gz
-rw-r--r-- root/root 5656320 2022-09-06 20:13 kernel
-rw-r--r-- root/root 80521551 2022-09-06 20:13 root.tlz
Тут у нас, предположительно, полный набор того, что необходимо для запуска:
root.tlz — корневая файловая система в tar + lzma;
kernel — ядро ОС;
initrd.gz — сжатый GZIP-архив начального диска;
csums.md5 — контрольные суммы.
Распаковываем архив:
$ tar xvf payload.tar
И сразу смотрим, что все файлы извлечены правильно:
$ md5sum -c csums.md5
initrd.gz: ЦЕЛ
kernel: ЦЕЛ
root.tlz: ЦЕЛ
Теперь будет полезно сверить типы:
$ file kernel initrd.gz root.tlz
kernel: Linux kernel x86 boot executable bzImage, version 4.4.218.x86_64.1 (root@blocks) #1 SMP Mon Mar 14 21:33:09 UTC 2022, RO-rootFS, swap_dev 0X5, Normal VGA
initrd.gz: LZMA compressed data, streamed
root.tlz: LZMA compressed data, streamed
Опа, еще одна любопытная подсказка. Файл initrd.gz почему-то определяется как LZMA-архив, несмотря на расширение. На самом деле внутри может быть все что угодно — от GZIP и LZMA до LZ4 или XZ. Как автору сборки захотелось, так он и упаковал. Давайте заглянем в него, чтобы убедиться:
$ xxd -l 32 initrd.gz
00000000: 5d00 0000 04ff ffff ffff ffff ff00 180d ]...............
00000010: dd04 6234 74af 7721 b6c0 09a5 3d6f 39cf ..b4t.w!....=o9.
Если бы это реально был GZIP, то «магические» байты начинались бы с 1F 8B. У нас же 5D 00 00. В стандартном листе сигнатур не находится, но быстрое гугление подтверждает догадку — это действительно LZMA, но старый формат.
Попытка распаковать «в лоб» заканчивается провалом:
$ unlzma -t initrd.gz
unlzma: initrd.gz: Сжатые данные повреждены
В этом случае я обычно беру свой любимый 7zip, который может справиться с чем угодно:
$ 7z x initrd.gz
7-Zip 24.09 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29
64-bit locale=ru_RU.UTF-8 Threads:8 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 4592051 bytes (4485 KiB)
Extracting archive: initrd.gz
WARNING:
initrd.gz
Cannot open the file as [gzip] archive
The file is open as [lzma] archive
--
Path = initrd.gz
Open WARNING: Cannot open the file as [gzip] archive
Type = lzma
Method = LZMA:26
ERROR: There are some data after the end of the payload data : initrd
Sub items Errors: 1
Archives with Errors: 1
Sub items Errors: 1
Как видим, расширение gz не обмануло этот чудесный архиватор. Он правильно распознал его как LZMA и распаковал initrd, сообщив о «мусоре» в конце файла. Тут важно понять, что это соответствует действительности. Еще раз натравливаем binwalk:
$ binwalk -e initrd.gz
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 LZMA compressed data, properties: 0x5D, dictionary size: 67108864 bytes, uncompressed size: -1 bytes
Как видим, «поток» только один, а это значит, что данные в хвосте файла утилите неизвестны. Это может быть просто заполнение 00/FF или какие-либо кастомные метаданные. Имеет смысл посмотреть:
$ xxd initrd.gz | tail -n 50
00460ea0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00460eb0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00460ec0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00460ed0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00460ee0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
…
Я не стал вставлять весь кусок, там одни нули. А это значит, что initrd — единственная полезная нагрузка. Еще раз заглядываем в файл, дабы убедиться:
$ file initrd
initrd: ASCII cpio archive (SVR4 with no CRC)
Замечательно, это архив, созданный с помощью одной из древних утилит — cpio, разработанной в AT&T (1977 год). Давайте сразу же посмотрим на содержимое:
$ cpio -it < initrd | less
bin
bin/ash
bin/cat
bin/chmod
bin/chown
bin/cp
bin/cpio
bin/date
bin/dd
bin/df
bin/dnsdomainname
bin/echo
…
Опять же полный вывод приводить нет смысла — тут у нас четко все, что нужно для первоначальной загрузки. Итак, на данный момент имеем (лишнее я из директории убрал):
$ ls -lha
-rw-rw-r-- 1 user user 16M сен 6 2022 initrd
-rw-r--r-- 1 user user 5,4M сен 6 2022 kernel
-rw-r--r-- 1 user user 77M сен 6 2022 root.tlz
Осталось разобраться с root.tlz. Конечно, можно попробовать распаковать сразу в отдельную директорию:
$ mkdir -p ./rootfs_unpack && lzma -dc root.tlz | tar xvf - -C ./rootfs_unpack
Вначале весело побегут файлы, но затем будет ошибка:
lzma: root.tlz: Сжатые данные повреждены
./apps/
tar: Завершение работы с состоянием неисправности из-за возникших ошибок
Значит, снова пора натравить binwalk и посмотреть, не подсовывают ли нам нули вместо конца файла:
$ binwalk root.tlz
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 LZMA compressed data, properties: 0x5D, dictionary size: 67108864 bytes, uncompressed size: -1 bytes
820220 0xC83FC JBOOT STAG header, image id: 2, timestamp 0x3B408865, image size: 2766533344 bytes, image JBOOT checksum: 0xAB24, header JBOOT checksum: 0x9CD6
2700160 0x293380 PGP RSA encrypted session key - keyid: A8CC3FC3 8471108 RSA (Encrypt or Sign) 1024b
18125214 0x114919E JBOOT STAG header, image id: 2, timestamp 0x45FAC370, image size: 2123779333 bytes, image JBOOT checksum: 0x27CF, header JBOOT checksum: 0xF1CE
20230481 0x134B151 JBOOT STAG header, image id: 12, timestamp 0x773C6383, image size: 2204064278 bytes, image JBOOT checksum: 0xB358, header JBOOT checksum: 0xD0CD
33967023 0x2064BAF JBOOT STAG header, image id: 7, timestamp 0x263F268A, image size: 2114150128 bytes, image JBOOT checksum: 0x4FF0, header JBOOT checksum: 0x394E
39598416 0x25C3950 JBOOT STAG header, image id: 5, timestamp 0xE76631F5, image size: 1227953069 bytes, image JBOOT checksum: 0x3003, header JBOOT checksum: 0x44D
70310055 0x430D8A7 MySQL ISAM compressed data file Version 3
75396371 0x47E7513 JBOOT STAG header, image id: 0, timestamp 0x202435CC, image size: 254701542 bytes, image JBOOT checksum: 0x9B48, header JBOOT checksum: 0xBC3C
Вот это уже действительно интересно. С начала файла у нас и правда LZMA-архив, но размер смущает. Может ли корневая ФС весить жалких 820 220 байт? Я, конечно, попробовал распаковать по этому смещению, но ожидаемо получил ошибку вида ERROR: Unexpected end of data.
Сигнатуры, найденные binwalk, скорее всего, совпадения, так что давайте попробуем проигнорировать нули и прочую дрянь, которая может быть в хвосте файла:
$ 7z x -so root.tlz | tar -xv --ignore-zeros --warning=no-unknown-keyword --no-same-owner -C rootfs_unpack
Ожидаемо, в итоге архиватор ругнулся:
ERROR: There are some data after the end of the payload data : root
tar: Завершение работы с состоянием неисправности из-за возникших ошибок
Содержимое rootfs_unpack демонстрирует успешную распаковку, ну а на мусор в конце можно не обращать внимания. Пора собрать из него образ.
Начинаем с заполненного нулями файла rootfs.img. Для наших целей вполне хватит 2 Гб:
$ dd if=/dev/zero of=rootfs.img bs=1M count=2048 status=progress
Делаем файловую систему EXT4:
$ mkfs.ext4 -F rootfs.img
Создаем временную директорию rnas_root, которая послужит точкой монтирования:
$ sudo mkdir -p /mnt/rnas_root
Собственно, монтируем:
$ sudo mount -o loop rootfs.img /mnt/rnas_root
Закидываем внутрь содержимое rootfs:
$ sudo cp -a rootfs_unpack/. /mnt/rnas_root/
Убеждаемся, что кэш сброшен на диск (в нашем случае внутрь rootfs.img):
$ sync
Размонтируем:
$ sudo umount /mnt/rnas_root
Эмуляция
Вот мы и добрались до этапа, на котором можно будет пробовать запускать ОС. Но перед этим нам явно понадобится создать хотя бы один виртуальный диск:
$ qemu-img create -f qcow2 data.qcow2 40G
Запускаемся через QEMU. Тут мы сразу делаем проброс порта 22 на локалхост 9022, указываем то, чтобы виртуальная машина использовала нашу сеть, через эмуляцию сетевой карты Intel E1000. Дальше подсовываем ядро, начальный диск, включаем временные метки для отладки и предотвращаем возможный бутлуп:
$ qemu-system-x86_64 \
-enable-kvm -cpu host -smp 2 -m 2048 \
-serial mon:stdio -display none \
-device e1000,netdev=n0 \
-netdev user,id=n0,hostfwd=tcp::9022-:22 \
-drive if=none,id=d0,file=data.qcow2,format=qcow2 \
-device ahci,id=ahci \
-device ide-hd,drive=d0,bus=ahci.0 \
-kernel ./kernel \
-initrd ./initrd \
-append "console=ttyS0,115200 printk.time=1 rdinit=/init panic=10"
[ 0.590518] gpio_it87: no device
[ 1.221393] locate_acpi_devs: failed to find GPIO gpio_ich.
[ 1.225265] readynas_led_probe: no compatible platform
Starting the boot process...
Detected system type: UNKNOWN
Loading kernel modules...done
Boot mode: Normal
Bringing up network...eth0.done
Bringing up RAID arrays...done
No partitions found on any disk! Going to factory default...
Stopping running RAID devices...done
Bringing up network...eth0.done
Creating partitions based on QEMU HARDDISK (40GB) disk...sda.done
Creating root filesystem...done
Extracting root image...ERROR: Could not mount boot flash [/dev/null1] (No such file or directory)
command failed: mdev -s
command failed: /usr/sbin/telnetd
Welcome to ReadyNASOS
nas-12-34-56 login: command failed: /sbin/getty 115200 console vt100
tar: can't open '/media/boot/root.tlz': No such file or directory
ERROR: Could not properly extract root image!
tar: can't open '/media/boot/root.tlz': No such file or directory
done
Creating data filesystem...done
ERROR: Registering data filesystem failed!
command failed: mdev -s
command failed: /usr/sbin/telnetd
Welcome to ReadyNASOS
nas-12-34-56 login:
Ого, мы получили приветствие ОС. Но в логах сразу две проблемы. Первая — Boot flash не смонтирован по причине отсутствия /media/boot/root.tlz. Вторая — mdev -s и telnetd падают, поскольку /proc, /sys и /dev/pts не готовы на этом этапе. Но все же давайте попробуем зайти с дефолтным логином и паролем (admin/password):
Sep 28 17:30:50 command failed: [ 299.160391] init[1]: segfault at 80 ip 00007f21b18a868b sp 00007ffd06d28190 error 4/sbin/getty 115200 console vt100
in libuClibc-1.0.9.so[7f21b187a000+70000]
[ 299.173519] Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b
[ 299.173519]
[ 299.174052] CPU: 1 PID: 1 Comm: init Tainted: P O 4.4.218.x86_64.1 #1
[ 299.174052] Hardware name: QEMU Ubuntu 25.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 299.174052] 0000000000000000 ffff88007e99bca8 ffffffff88364895 ffffffff88b58612
[ 299.174052] 0000000000000000 ffff88007e99bd20 ffffffff880dd0ff ffff880000000010
[ 299.174052] ffff88007e99bd30 ffff88007e99bcd0 ffffffff880dacdd 000000000000000b
[ 299.174052] Call Trace:
[ 299.174052] [<ffffffff88364895>] dump_stack+0x57/0x6d
[ 299.174052] [<ffffffff880dd0ff>] panic+0xbe/0x20c
[ 299.174052] [<ffffffff880dacdd>] ? perf_event_exit_task+0x2ba/0x2c9
[ 299.174052] [<ffffffff8806610c>] do_exit+0x4af/0x8fc
[ 299.174052] [<ffffffff8806661a>] do_group_exit+0x95/0x95
[ 299.174052] [<ffffffff8806e5ff>] get_signal+0x308/0x58e
[ 299.174052] [<ffffffff88002cad>] do_signal+0x23/0x507
[ 299.174052] [<ffffffff883ad749>] ? vgacon_scroll+0x288/0x288
[ 299.174052] [<ffffffff880dd90e>] ? printk+0x4b/0x4d
[ 299.174052] [<ffffffff88031c01>] ? bad_area+0x42/0x47
[ 299.174052] [<ffffffff88031f97>] ? __do_page_fault+0x259/0x336
[ 299.174052] [<ffffffff88001024>] exit_to_usermode_loop+0x24/0x6d
[ 299.174052] [<ffffffff88001419>] prepare_exit_to_usermode+0x1f/0x32
[ 299.174052] [<ffffffff888ea437>] retint_user+0x8/0x29
[ 299.174052] Kernel Offset: disabled
[ 299.174052] Rebooting in 10 seconds..
Упс. Getty дергают на console, а не на ttyS0. Итогом становится падение в uClibc и скоропостижный kernel panic по смерти PID 1. Суммарно у нас три проблемы, которые предстоит решить перед достижением стабильного запуска. Но главной, на мой взгляд, является отсутствие /media/boot/root.tlz.
В идеальном мире имеет смысл открыть init и попытаться понять, что ждет система. Но у нас такой роскоши нет, ведь тут он представляет собой бинарник. А значит, придется все делать руками, благо мы знаем, где в итоге должен оказаться root.tlz.
Создадим небольшой образ виртуальной флешки на 256 Мб:
$ qemu-img create -f qcow2 bootflash.qcow2 256M
Нам надо работать с ним так, как с локальным диском. Поэтому воспользуемся модулем ядра NBD (Network Block Device):
$ sudo modprobe nbd max_part=8
QEMU имеет в составе удобную утилиту, которая позволяет монтировать сетевые диски в качестве локальных. В нашем случае его роль играет образ в формате qcow2:
$ sudo qemu-nbd -c /dev/nbd0 bootflash.qcow2
Итак, у нас есть /dev/nbd0, на котором мы делаем MBR-разметку:
$ sudo parted /dev/nbd0 mklabel msdos
Теперь крафтим раздел в формате ext2. Он займет всю емкость виртуального диска:
$ sudo parted -a optimal /dev/nbd0 mkpart primary ext2 1MiB 100%
На всякий случай повесим на него метку загрузочного:
$ sudo mkfs.ext2 /dev/nbd0p1 -L BOOT
Создаем временную точку монтирования:
$ sudo mkdir -p /mnt/bootflash
Монтируем наш раздел в /mnt/bootflash
$ sudo mount /dev/nbd0p1 /mnt/bootflash
Хоть я и не знаю содержимое init, но предположил, что система будет ожидать root.tlz или в корне флешки, или в отдельной директории boot. Поэтому создал ее:
$ sudo mkdir -p /mnt/bootflash/boot
И положил файл в обе локации:
$ sudo cp root.tlz /mnt/bootflash/
$ sudo cp root.tlz /mnt/bootflash/boot/
Убедился, что все записалось:
$ sudo sync
Размонтировал раздел:
$ sudo umount /mnt/bootflash
И отключил виртуальное устройство:
$ sudo qemu-nbd -d /dev/nbd0
Теперь запустил эмуляцию, добавив флешку:
$ qemu-system-x86_64 \
-enable-kvm -cpu host -smp 2 -m 2048 \
-serial mon:stdio -display none \
-device e1000,netdev=n0 \
-netdev user,id=n0,hostfwd=tcp::9022-:22 \
-device ahci,id=ahci \
-drive if=none,id=boot,file=bootflash.qcow2,format=qcow2 \
-device ide-hd,drive=boot,bus=ahci.0 \
-drive if=none,id=d0,file=data.qcow2,format=qcow2 \
-device ide-hd,drive=d0,bus=ahci.1 \
-kernel ./kernel \
-initrd ./initrd \
-append "console=ttyS0,115200 printk.time=1 panic=10"
Увы, несмотря на всю проведенную подготовку, файл root.tlz так и не был найден. Ошибка выдавалась та же самая, поэтому пришла пора ручной диагностики и попытки запуска. Для этого в команду был добавлен обход init и передача управления пользователю через стандартный шелл: rdinit=/bin/sh.
$ qemu-system-x86_64 \
-enable-kvm -cpu host -smp 2 -m 2048 \
-serial mon:stdio -display none \
-device e1000,netdev=n0 \
-netdev user,id=n0,hostfwd=tcp::9022-:22 \
-device ahci,id=ahci \
-drive if=none,id=boot,file=bootflash.qcow2,format=qcow2 \
-device ide-hd,drive=boot,bus=ahci.0 \
-drive if=none,id=d0,file=data.qcow2,format=qcow2 \
-device ide-hd,drive=d0,bus=ahci.1 \
-kernel ./kernel \
-initrd ./initrd \
-append "console=ttyS0,115200 printk.time=1 panic=10 rdinit=/bin/sh"
Теперь вручную подготовлю запуск. Монтирую базовые псевдо-ФС, без них жить не будет:
/# mount -t proc proc /proc
/# mount -t sysfs sys /sys
/# mount -t devtmpfs devtmpfs /dev
Чтобы точно получить /dev/sda1, принудительно создаю:
/# mknod /dev/sda1 b 8 1
Делаю точку монтирования:
/# mkdir -p /media/boot
Монтирую образ флешки в нужное место:
/# mount -t ext2 -o ro /dev/sda1 /media/boot
Убеждаюсь, что root.tlz виден:
/# ls /media/boot
boot lost+found root.tlz
Дальше пускай вендорский бинарник делает свое грязное дело. Теперь я уверен, что он увидит искомый файл:
/# exec /init
Starting the boot process...
ERROR: Mounting pseudo-filesystems failed! (Device or resource busy)
Bringing up network...eth0.done
command failed: mdev -s
command failed: /usr/sbin/telnetd
Welcome to ReadyNASOS
readynas login:
Ошибка ушла, а это значит, что init нашел сжатый образ корневой ФС и успешно его распаковал. Еще раз вводим дефолтный логин и пароль:
readynas login: admin
Password:
Login incorrect
readynas login: admin
Password:
Login incorrect
readynas login: admin
Password:
Login incorrect
После третьего неправильного ввода система отправляется в ребут. Неужели все было зря?
Вместо заключения
Итак, мы успешно дошли до приглашения ОС и решили вопрос с паникой ядра из-за отсутствия корневой ФС. Теперь нужно более детально заниматься изучением и изменением /etc/passwd и /etc/shadow, чтобы получить доступ внутрь системы. После этого перепаковать root.tlz.
Текущий запуск был наполовину ручным. Нужно будет написать минимальную обертку — иначе каждый раз придется вводить кучу команд, чтобы добраться до окна авторизации. В общем, дальнейший ресерч — тема для отдельной большой публикации.
Пора закругляться, надеюсь, мой опыт был полезен. Ну а если у вас получится добиться стабильного запуска ReadyNAS OS, обязательно напишите об этом в комментариях.
litalen
Нет :) Поставьте простой эксперимент:
fdisk ищет MBR/GPT-сигнатуру, и если находит - то выводит таблицу разделов, и только. Если ее нет - он выведет данные о файле, но не более того, никакой реальной характеристики его содержимого он не даст. Можно было сразу пропускать этот этап и переходить к binwalk ;)
Так чтобы его значительно ускорить можно не копировать по байтику, особенно учитывая что смещения как правило выровненные:
т.е. в вашем случае
dd if=ReadyNASOS-6.10.8-x86_64.img of=payload.tar bs=4096 skip=$((16384/4096)) status=none
илиdd if=ReadyNASOS-6.10.8-x86_64.img of=payload.tar bs=4096 skip=4 status=none
.Для русскоговорящего выглядит забавно :D