zImage 是編譯內(nèi)核后在 arch/arm/boot
目錄下生成的一個(gè)已經(jīng)壓縮過的內(nèi)核映像。通常我們不會(huì)使用編譯生成的原始內(nèi)核映像 vmlinux
,因其體積很大。因此,zImage 是我們最常見的內(nèi)核二進(jìn)制,可以直接嵌入到固件,也可以直接使用 qemu 進(jìn)行調(diào)試。當(dāng)然,在 32 位嵌入式領(lǐng)域還能見到 uImage
,這是在 zImage 首位增加 64B 的頭,描述映像文件類型、加載位置、內(nèi)核大小等信息。
有些嵌入式設(shè)備的文件系統(tǒng)直接嵌入到內(nèi)核中,這種內(nèi)置文件系統(tǒng)的機(jī)制被稱為 ramdisk/initramfs
,如果只是使用 extract-vmlinux/binwalk
解壓固件,釋放大量 shell 腳本和配置文件,是很容易做到的,但是如果想要修改這些文件,并進(jìn)行重新打包,生成實(shí)際設(shè)備可以運(yùn)行的 zImage
內(nèi)核映像可能不是那么簡單。
本文將演示如何在 32位 ARM zImage 中替換 piggy 中的文件系統(tǒng),我們以 openWRT 的某個(gè)版本固件為例進(jìn)行講解。
初始設(shè)置
下載 OpenWRT ARM zImage-initramfs 映像,這是一個(gè)基于 ramdisk
的典型內(nèi)核映像,不需要額外的文件系統(tǒng),實(shí)際上也無法使用 binwalk
直接提取我們想要修改的操作系統(tǒng)啟動(dòng)提示信息。
$ wget https://downloads.openwrt.org/releases/17.01.0/targets/armvirt/generic/lede-17.01.0-r3205-59508e3-armvirt-zImage-initramfs -O zImage-initramfs
$ openssl dgst zImage-initramfs
SHA256(zImage-initramfs)= 5ad269e95b2db16aea3794dd0e97dabb6f9712184d79b0764bb10a810f8d7639
使用 qemu 啟動(dòng)
$ qemu-system-arm -M virt -m 1024 -kernel zImage-initramfs -append "console=ttyAMA0" -nographic
最小 shell
控制臺(tái)
BusyBox v1.25.1 () built-in shell (ash)
_________
/ /\ _ ___ ___ ___
/ LE / \ | | | __| \| __|
/ DE / \ | |__| _|| |) | _|
/________/ LE \ |____|___|___/|___| lede-project.org
\ \ DE /
\ LE \ / -----------------------------------------------------------
\ DE \ / Reboot (17.01.0, r3205-59508e3)
\________\/ -----------------------------------------------------------
=== WARNING! =====================================
There is no root password defined on this device!
Use the "passwd" command to set up a new password
in order to prevent unauthorized SSH logins.
--------------------------------------------------
root@LEDE:/#
查看內(nèi)核版本,找到對應(yīng)的源碼,因?yàn)槲覀冇锌赡軙?huì)根據(jù)內(nèi)核解壓縮的源碼,調(diào)整重打包方式。
root@LEDE:/# uname -a
Linux LEDE 4.4.50 #0 SMP Mon Feb 20 17:13:44 2017 armv7l GNU/Linux
找到相應(yīng)版本的內(nèi)核,推薦在線瀏覽 https://elixir.bootlin.com/linux/v4.4.50/source/,版本匹配也沒有那么重要,因?yàn)閮?nèi)核解壓縮的核心代碼其實(shí)一直以來變化不大,位于源碼目錄 arch/arm/boot/compressed
。
提取 Piggy
使用 binwalk
分析固件,就像我們在開始說的,binwalk
可能可以提取其中的配置文件,也有可能無法提取,即使提取,也都是歸在一個(gè)文件夾下,并沒有常見的 squashfs
文件系統(tǒng)
$ binwalk zImage-initramfs
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 Linux kernel ARM boot executable zImage (little-endian)
15400 0x3C28 xz compressed data
15632 0x3D10 xz compressed data
毫無疑問,固件開始部分是可以直接運(yùn)行的未經(jīng)壓縮的用于解壓內(nèi)核的 head.o
和 misc.o
,使用 dd
命令提取該部分進(jìn)行分析,或者直接將整個(gè)固件拖入 IDA,選擇 arm,并只反匯編固件頭部部分。
運(yùn)行上述 IDC 腳本,即可得到解壓內(nèi)核代碼部分??梢詫Ρ葍?nèi)核源碼,我們需要找到固件中,內(nèi)核壓縮映像文件的起始地址和結(jié)束地址。piggy.S
使用 incbin
關(guān)鍵字引入 piggy.gz
。其中全局變量 input_data
和 input_data_end
分別是 piggy 的起始地址和結(jié)束地址。
.section .piggydata,#alloc
.globl input_data
input_data:
.incbin "arch/arm/boot/compressed/piggy.gz"
.globl input_data_end
input_data_end:
毫無疑問,內(nèi)核解壓代碼需要這些全局變量,這樣才能夠解壓真正壓縮的內(nèi)核。
putstr("Uncompressing Linux...");
ret = do_decompress(input_data, input_data_end - input_data,
output_data, error);
if (ret)
error("decompressor returned an error");
else
putstr(" done, booting the kernel.\n");
IDA 反編譯的固件頭部,尋找 Uncompressing Linux...
,對比源碼很容易知道 piggy 的實(shí)際偏移。
繼續(xù)分析匯編,找到全局變量存放的位置
對比原始固件二進(jìn)制時(shí),發(fā)現(xiàn)壓縮結(jié)束 magic YZ 后面多出了 4B 數(shù)據(jù),這 4B 其實(shí)是原始未經(jīng)壓縮的xz大小。實(shí)際上 YZ
才是壓縮文件的結(jié)尾。因此使用 xz
解壓時(shí),估計(jì)會(huì)出現(xiàn) Unexpected end of input
錯(cuò)誤,只需要添加參數(shù)即可。dd
截取 piggy
$ dd if=zImage-initramfs of=vmlinux.xz bs=1 skip=$[0x3d10] count=$[0x2bb404]
2864132+0 records in
2864132+0 records out
2864132 bytes (2.9 MB, 2.7 MiB) copied, 13.595 s, 211 kB/s
解壓 piggy
$ unxz --verbose --single-stream < vmlinux.xz > /tmp/vmlinux
100 % 2,797.0 KiB / 8,883.5 KiB = 0.315
我們發(fā)現(xiàn)解壓后的 vmlinux
內(nèi)核映像大小果然是 28 c3 8a 00
$ ls -l /tmp/vmlinux
-rw-r--r-- 1 kali kali 9096744 Dec 20 04:04 /tmp/vmlinux
$ python -c "print(0x8ace28)"
9096744
重打包
修改 vmlinux
,例如修改啟動(dòng)界面字符串,找到需要修改信息的地址。這些信息顯示 initramfs
嵌入在解壓后的 vmlinux
中,該部分由一個(gè)沒有校驗(yàn)和的未經(jīng)壓縮的 CPIO 文檔組成(binwalk
可以識(shí)別)。
$ strings -t x /tmp/vmlinux | grep "WARNING\!"
76ac3a === WARNING! =====================================
使用 hexedit
編輯,回車鍵可快速定位此地址,tab
可切換 16 進(jìn)制 / ASCII 碼,ctrl+x
保存并退出。
0076AC3C 3D 20 57 41 52 4E 49 4E 47 21 20 4D 6F 64 69 66 69 63 61 74 = WARNING! Modificat
0076AC50 69 6F 6E 20 73 75 63 63 65 65 64 65 64 21 21 21 3D 3D 3D 3D ion succeeded!!!====
如果直接使用 xz
壓縮,我們會(huì)發(fā)現(xiàn)壓縮后大小大于原始?jí)嚎s文件 0x2bb404(2864132),通過 Linux 源碼可以找到壓縮命令位于 xz_wrap.sh
xz --check=crc32 --arm --lzma2=$LZMA2OPTS,dict=32MiB
僅僅使用上述命令壓縮還是不夠的,壓縮后的文件仍然較大,nice
可以達(dá)到最大壓縮比。最終壓縮命令如下
$ xz --check=crc32 --arm --lzma2=,dict=32MiB,nice=128 < /tmp/vmlinux > /tmp/vmlinux.xz
$ ls -l /tmp/vmlinux.xz
-rw-r--r-- 1 kali kali 2863832 Dec 20 04:26 /tmp/vmlinux.xz
顯然小于原始?jí)嚎s文件,符合要求。要記住,piggy 末尾 4 字節(jié)存放原始文件大小,而我們只是修改啟動(dòng)信息,并沒有改變原始 vmlinux
大小
$ echo -en "\x28\xce\x8a\x00" >> /tmp/vmlinux.xz # piggy.gz
替換 piggy
$ cp zImage-initramfs zImage-initramfs-warnmod
$ dd if=/tmp/vmlinux.xz of=zImage-initramfs-warnmod bs=1 seek=$[0x3d10] conv=notrunc
2863836+0 records in
2863836+0 records out
2863836 bytes (2.9 MB, 2.7 MiB) copied, 11.1713 s, 256 kB/s
修改內(nèi)核解壓代碼中的 piggy 結(jié)束地址,input_data_end = hex(0x3d10+2863836) = 0x2befec
,原始大小為 0x2bf114
002BF124 EC EF 2B 00 68 F5 2B 00 10 3D 00 00 64 F5 2B 00 64 F1 2B 00 ..+.h.+..=..d.+.d.+.
嘗試啟動(dòng)內(nèi)核,修改成功!
BusyBox v1.25.1 () built-in shell (ash)
_________
/ /\ _ ___ ___ ___
/ LE / \ | | | __| \| __|
/ DE / \ | |__| _|| |) | _|
/________/ LE \ |____|___|___/|___| lede-project.org
\ \ DE /
\ LE \ / -----------------------------------------------------------
\ DE \ / Reboot (17.01.0, r3205-59508e3)
\________\/ -----------------------------------------------------------
=== WARNING! Modification succeeded!!!============
There is no root password defined on this device!
Use the "passwd" command to set up a new password
in order to prevent unauthorized SSH logins.
--------------------------------------------------
root@LEDE:/#
小結(jié)
如果需要增加而不是修改 initramfs
的內(nèi)容,可能就沒那么簡單了。因?yàn)槟阈枰獪?zhǔn)確掌握固件的每一個(gè)部分,而且需要注意的是 piggy 的 inflated size
也就是 xz 實(shí)際大小其實(shí)是 input_data_end - 4
,這一部分代碼位于 misc.c
的 LC0
對象
LC0: .word LC0 @ r1
.word __bss_start @ r2
.word _end @ r3
.word _edata @ r6
.word input_data_end - 4 @ r10 (inflated size location)
.word _got_start @ r11
.word _got_end @ ip
.word .L_user_stack_end @ sp
.word _end - restart + 16384 + 1024*1024
.size LC0, . - LC0
以本文中的固件為例,piggy 實(shí)際大小 0x2bf110
,位于固件偏移 0x258
,因此如果修改了 piggy 的大小,還需要修改此處地址對應(yīng)的數(shù)據(jù)。
當(dāng)然,實(shí)際還需要考慮各個(gè)部分的偏移,可參考 https://gist.github.com/jamchamb/243e6973aeb5c9a2e302a4d4f57f16e1
如果你需要增加內(nèi)核內(nèi)容并且改變了原有內(nèi)核大小,而不只是簡單修改,則需要掌握內(nèi)核解壓縮的詳細(xì)流程,在這里,我們只將內(nèi)核壓縮映像生成流程簡單呈現(xiàn)如下,詳細(xì)流程可參見
vmlinux
│
│ -R.note
│ -R.comment
│
└─arch/arm/boot/Image
│
│ gzip -f -9 < Image > piggy.gz
│
└─arch/arm/boot/compressed/piggy.gz
│
│ piggy.S 直接引入piggy.gz
│
└─arch/arm/boot/compressed/piggy.o
│
│ +head.o
│ +misc.o
│
└─arch/arm/boot/compressed/vmlinux
│
│ -debuginfo
│
└─arch/arm/boot/compressed/zImage
內(nèi)核代碼中的 head.S
和 misc.c
用于內(nèi)核自解壓,所以,如果我們需要直接通過修改內(nèi)核二進(jìn)制的方式打 patch,則需要了解內(nèi)核壓縮和解壓的流程。從上圖也可以看出來,piggy 就是壓縮過的內(nèi)核的一部分,其實(shí)也是內(nèi)核的主體部分。文章來源:http://www.zghlxwxcb.cn/news/detail-499523.html
參考文獻(xiàn)
Modifying Embedded Filesystems in ARM Linux zImages
Linux內(nèi)核源碼分析–內(nèi)核啟動(dòng)之zImage自解壓過程
Linux2.6 內(nèi)核啟動(dòng)分析
initramfs 在內(nèi)核中的作用與實(shí)現(xiàn)文章來源地址http://www.zghlxwxcb.cn/news/detail-499523.html
到了這里,關(guān)于修改嵌入式 ARM Linux 內(nèi)核映像中的文件系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!