busybox最小Linux系統

Nomaldisk發表於2025-01-06

環境

WSL(Ubuntu 22.04)

建立磁碟映像

可以使用fallocate為磁碟映像分配一塊空間,或者使用dd if=/dev/zero of=$img bs=1M count=$size_in_MB直接得到一個大小為$size_in_MB大小的檔案。

使用mkfs.ext4格式化映像檔案,並使用mount -o loop $img mnt將檔案掛載。

如果想要在磁碟映像中分割槽,則可以先使用fdiskcfdisk對磁碟映像進行分割槽,然後使用losetup -fP $img將檔案掛載為迴環裝置。這裡-f參數列示自動尋找可以掛載的迴環裝置號,-P參數列示探測檔案中的分割槽並分別掛載為迴環裝置。掛載為迴環裝置後,再使用mount $loop1 $mnt1等命令掛載迴環裝置。

構建busybox

下載busybox原始碼並構建,這裡使用的是busybox-1.36.1版本

這裡採用的構建選項有

構建靜態檔案:

Symbol: STATIC [=y]
Prompt: Build static binary (no shared libs)
	Defined at Config.in:362
	Location:
		-> Settings

這個版本預設支援了Unicode,可以不用更改

Symbol: UNICODE_SUPPORT [=y]
	Prompt: Support Unicode
	Defined at libbb/Config.in:311
	Location:
		-> Settings

新增了Unicode寬字元支援

Symbol: UNICODE_WIDE_WCHARS [=y]
	Prompt: Allow wide Unicode characters on output
	Defined at libbb/Config.in:390
		Depends on: UNICODE_SUPPORT
		Location:
		-> Settings
			-> Support Unicode (UNICODE_SUPPORT [=y])

其他構建選項均可以不更改

使用make構建後,再使用make install即可將完整的busybox、busybox符號連結等檔案安裝到busybox原始碼目錄下的_install目錄內。或者可以透過make install CONFIG_PREFIX=$install將busybox安裝到指定目錄中。比如這裡我們可以使用make install CONFIG_PREFIX=$mnt將busybox安裝到已經掛載的磁碟映像中。

構建Linux核心

下載Linux核心原始碼,這裡使用Linux-6.12.7版本

根據自己喜好配置即可

建立rootfs

這裡需要建立一個rootfs來作為Linux執行的環境。

檢視busybox的安裝目錄可以發現,目前只有binsbinusr三個目錄和linuxrc一個符號連結。對比我們自己的Linux根目錄可以發現,我們大概有以下目錄

bin boot dev etc home lib mnt opt proc root run sbin sys tmp usr var

那麼我們在$mnt目錄下建立這些目錄即可。

由於mount需要sudo$mnt目錄下的檔案很可能是root許可權,後面一系列操作可能都需要root許可權。

現在可以chroot$mnt目錄下試試能否使用shell。

執行虛擬機器

這裡我們使用qemu虛擬機器。

將啟動命令寫成一個指令碼

#!/bin/sh
/usr/bin/qemu-system-x86_64\
  -kernel path/to/bzImage\
  -hda path/to/rootfs.img\
  -nographic\
  -append "console=ttyS0 root=/dev/sda init=/linuxrc"
  • -kernel選項表示設定Linux kernel為bzImage
  • -hda選項表示選擇磁碟映像
  • -nographic表示不使用qemu視窗,而是將輸出重定向到終端
  • -append表示傳遞給Linux核心的引數
    • console=ttyS0表示將輸出重定向到串列埠裝置ttyS0,這將使qemu將啟動階段的資訊輸出到終端
    • root=/dev/sda表示根檔案系統的位置,虛擬機器中一般是sda
    • init=linuxrc表示使用linuxrc作為init程序,也就是Linux下的第一個程序啟動,這個linuxrc其實就是我們的busybox

啟動配置

此時如果直接執行指令碼啟動虛擬機器可能會報錯,因為我們沒有配置busybox作為init程序時的行為。

linuxrc會讀取/etc/inittab檔案,我們將該檔案配置如下

::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

該檔案內每行有四個欄位,格式為<id>:<runlevel>:<action>:<process>

  • <id>指編號,不重複即可
  • <runlevel>指執行級別,可以不指定,指定時表示執行級別為n時啟用改行的規則
  • <action>包含一系列動作,表示對登記的<process>在一定條件下執行的動作
  • <process>即要執行的程序,前面加上-表示以互動方式執行

<action>包含以下動作

action 含義
respawn 當process終止後馬上啟動一個新的
wait 當進入指定的runlevels後process才會啟動一次,並且到離開這個runlevels終止
initdefault 設定預設的執行級別,即我們開機之後預設進入的執行級別,不能是0,6,你懂的
sysinit 系統初始化,只有系統開機或重新啟動的時候,這個process才會被執行一次
powerwait 當init接收到電源失敗訊號的時候執行相應的process,並且如果init有程序在執行,會等待這個程序完成之後,再執行相應的process
powerfail 當init接收到電源失敗訊號的時候執行相應的process,並且如果init有程序在執行,不會等待這個程序完成,它會直接執行相應的process
powerokwait 電源已經故障,但是在等待執行對應操作的時候突然來電了就執行對應的process
powerfailnow 當電源故障並且init被通知UPS電源已經快耗盡執行相對應的process
ctrlaltdel 當使用者按下ctrl+alt+del這個組合鍵的時候執行對應的process
boot 只有在引導過程中,才執行該程序,但不等待該程序的結束;當該程序死亡時,也不重新啟動該程序
bootwait 只有在引導過程中,才執行該程序,並等待程序的結束;當該程序死亡時,也不重新啟動該程序
off 如果process正在執行,那麼就發出一個警告訊號,等待20秒後,再透過殺死訊號強行終止該process。如果process並不存在那麼就忽略該登記項
once 啟動相應的程序,但不等待該程序結束便繼續處理/etc/inittab檔案中的下一個登記項;當該程序死亡時,init也不重新啟動該程序

inittab第一行表示在系統啟動時,執行/etc/init.d/rcS指令碼里的內容。這也是沒有inittablinuxrc的預設動作。

接下來我們配置/etc/init.d/rcS指令碼的內容

#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH

runlevel=S
umask 022
export PATH LD_LIBRARY_PATH runlevel

# devices
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
mount -o remount,rw /

mdev -s

我們的指令碼配置了環境變數,裝置等,需要在系統啟動時進行的配置,開啟的服務,都可以在該檔案中進行配置。

配置完成後一定要賦予/etc/init.d/rcS執行許可權,否則啟動過程中會報錯。

此時啟動虛擬機器可以看到,我們已經進入了shell。

其他配置檔案

雖然我們的Linux已經正常啟動,但是不要高興的太早。

我們在shell中執行export PS1='\u@\h \W',重新登陸,我們預期會顯示root@host ~,但是,這裡並沒有我們的使用者名稱和主機名。

此時我們執行idhostname命令會發現,我們現在雖然是uid=0 gid=0的使用者,但是我們沒有使用者名稱,主機名也是(none)。執行ifconfig會發現,我們也沒有可用網路。

接下來我們將進行這些方面的配置。

我們的Linux已經可以啟動,而且busybox內建了vi作為編輯器,接下來的配置可以不透過宿主機,直接在虛擬機器中完成。

使用者配置

由於root使用者本來就存在,我們不能用adduser建立使用者,於是我們手動建立使用者屬性檔案。

Linux透過識別/etc/passwd中的使用者來判斷使用者名稱,我們手動建立這個檔案。

新增以下內容

root:x:0:0::/root:/bin/sh

這個檔案有7個欄位,格式為<user>:<passswd>:<uid>:<gid>:<desc>:<home>:<shell>

其中<passwd>欄位內容為加密後的密碼,如果設為空則表示不需要密碼也可以登入,如果為x表示密碼儲存在/etc/shadow檔案中。

如果我們不建立/etc/shadow檔案,passwd命令會將加密的密碼儲存在/etc/passwd中,所以我們打算建立一個/etc/passwd

我們的Linux和busybox都支援解析/etc/shadow檔案,接下來我們手動建立這個檔案。

新增以下內容

root::1::::::

這個檔案內每行9個欄位,格式為login:encyrptedpassword:lastchangedate:min_age:max_age:warning:inactivity:expiration_date:reserved,第一個欄位為使用者名稱,第二個欄位為加密後的密碼,如果為空會登入失敗,為*!時情況不確定,Linux console上寫*!表示沒有密碼,但實際測試後發現,為這兩個符號時,busybox的login會提示bad salt

後面的幾個欄位都與密碼修改時間有關,分別為

  • lastchange表示上次修改密碼的日期的時間,如果該值為0,則表示使用者下次登入時必須更改密碼
  • minage表示更改密碼的間隔日期,為空或為0表示隨時可以更改密碼
  • maxage表示必須更改密碼的日期
  • warning表示在密碼到期前n天警告使用者需要更改密碼
  • inactivity表示密碼過期後,n天內可以再更改密碼
  • expiration_date表示到期日期,到期後無法再登入
  • reserved最後一個欄位為保留欄位

有這個檔案後我們就可以使用passwd命令更改密碼,然後再檢視/etc/shadow可以發現密碼已經改變了。

然後我們就可以透過登入的方式進入作業系統。

更改/etc/inittab如下

::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L console 0 vt100
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

這表示不直接開啟一個shell,而是在console這個tty上開啟一個login

主機配置

一般我們將主機名寫在/etc/hostname中,但是busybox不自動讀取這個檔案。

於是我們新增配置到/etc/init.d/rcS

#!/bin/sh
...
# hostname
hostname -F /etc/hostname

這代表從/etc/hostname載入主機名

網路配置

同樣在/etc/init.d/rcS中新增以下配置

# network
ifconfig lo up
ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
route add default gw 192.168.1.1 eth0

ip地址隨意填寫,閘道器地址填寫為qemu外部提供的網路卡地址

網路卡配置

在WSL中,需要建立一張虛擬網路卡裝置作為虛擬機器的閘道器。

我們建立一張tap裝置,向網路卡配置指令碼中寫入以下內容

ip tuntap add dev tap0 mode tap
ip link set dev tap0 up
ip a add dev tap0 192.168.1.1/24
iptables -t nat -A POSTROUTING -o eth0 -s 192.168.1.1/24 -j MASQUERADE
echo 1 > /proc/sys/net/ipv4/ip_forward

這個指令碼建立了一張tap0網路卡,並分配了ip地址192.168.1.1,就是我們的虛擬機器的閘道器地址。

iptables命令建立了一條nat規則,將內部發出的源地址為192.168.1.0/24網段的資料包改為從eth0發出,這樣就可以讓虛擬機器連線到外部網路了。

此時進入虛擬機器,執行ping 192.168.1.1發現有網路連線。

然後執行cat nameserver 8.8.8.8 > /etc/resolv.conf配置域名解析伺服器。

此時執行ping www.baidu.com就可以ping通了。

由於busybox沒有自帶curl,執行echo -e "GET / HTTP/1.1\r\nHost:www.baidu.com\r\n\r\n" | nc www.baidu.com 80代替,可以收到html網頁內容。

相關文章