一次服务器BMC固件逆向经历

导出文件

访问BMC默认开启的Web管理界面http://$ip,开启ssh服务,之后即可以执行ssh root@$ip,得到一个受限的管理界面,功能很少,没有系统shell。

在服务器上执行ipmitool fru可以查找到设备型号,使用厂商提供的flash备份工具导出flash,比较慢,每秒200多KB。在BMC网页界面的BMC System Audit Log中看到BMC的Linux系统日志:

1
2
3
4
5
6
7
8
9
10
kernel: Kernel command line: root=/dev/ramdisk ro ip=none ramdisk_blocksize=4096 console=ttyS4,38400 rootfstype=cramfs bigphysarea=6144 imagebooted=1
kernel: Memory: 201216KB available (3080K code, 261K data, 128K init)
kernel: Ractrends Flash mapping: 0x2000000 at 0x20000000
kernel: Creating 6 MTD partitions on "Ractrends":
kernel: 0x00000000-0x02000000 : "fullpart"
kernel: 0x00050000-0x000c0000 : "conf"
kernel: 0x000d0000-0x00140000 : "bkupconf"
kernel: 0x00150000-0x00f10000 : "root"
kernel: 0x010c0000-0x01410000 : "www"
kernel: 0x01600000-0x01ff0000 : "lmedia"

从中得到flash的分区信息,包含conf bkupconf root www lmedia等分区。用binwalk查看导出的flash可以看到u-boot和一个存储内核的u-boot legacy image。

根据Linux系统日志中描述的conf偏移提取conf,binwalk可以确定文件系统为[JFFS2],而报告的很多zlib压缩块则是JFFS2的数据。本机模拟一个mtdblock设备并挂载:

1
2
3
4
modprobe mtdram total_size=32768 erase_size=64
modprobe mtdblock
dd if=conf of=/dev/mtdblock0
mount -t jffs2 /dev/mtdblock0 /mnt

发现很多文件是通常Linux系统/etc下的配置文件。如果调大conf结束偏移量,还能看到更多文件,不清楚模拟出来的mtdblock有哪些差异。

修改登录shell

猜测conf分区会被作为/etc使用,因此修改flash中conf分区处的passwd,把root的shell改成/bin/shumount /mnt后再次执行上述挂载操作,在dmesg中能看到内核输出的JFFS2 checksum错误,其中包含正确的校验和。在flash中搜索错误的校验和,修改成正确的。用flash备份工具写入修改后的flash,稍等片刻等待BMC自动重启,再次执行ssh即看到shell换成了busybox!实际测试没有这么顺利,修改了几次都发现没效果,原因是系统检测到JFFS2文件系统checksum错误后自动还原分区。

查看挂载点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
BusyBox v1.13.2 (2015-01-08 17:43:56 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
# mount
rootfs on / type rootfs (rw)
/dev/root on / type cramfs (ro)
/dev/proc on /proc type proc (rw)
sys on /sys type sysfs (rw)
/dev/shm on /var type tmpfs (rw)
/dev/ram2 on /usr/local/www type cramfs (ro)
/dev/mtdblock1 on /conf type jffs2 (rw)
/dev/mtdblock2 on /bkupconf type jffs2 (rw)
/dev/mtdblock5 on /usr/local/lmedia type jffs2 (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
tmpfs on /usr/local/www/tmp type tmpfs (rw)
/dev/mtdblock5 on /usr/local/www/blackbox type jffs2 (rw)
/dev/shm on /usr/local/tmplmedia type tmpfs (rw)
# cat /etc/fstab
/dev/mtdblock1 /conf jffs2 defaults 0 0
/dev/mtdblock2 /bkupconf jffs2 defaults 0 0
/dev/root / auto defaults,rw 0 0
/dev/mtdblock5 /usr/local/lmedia jffs2 defaults 0 0
proc /proc proc defaults 0 0
sys /sys sysfs defaults
devpts /dev/pts devpts gid=5,mode=620

注意/dev/mtdblock{0..5},它们是同一块SPI flash,kernel中应该编码了划分方案,但尚不清楚这个信息是怎么存储的。

导出initrd

内核启动选项为root=/dev/ramdisk ro ip=none ramdisk_blocksize=4096 console=ttyS4,38400 rootfstype=cramfs bigphysarea=6144 imagebooted=1/dev/ramdisk即为u-boot提供的initrd,使用只读文件系统[cramfs],启动后不会通过switch_root等方式更换根文件系统。可以用ssh root@$ip cat /dev/ramdisk把initrd复制到本地,挂载后发现/etc下有很多指向/conf的软链接,比如/etc/passwd -> /conf/passwd。这就解释了为什么之前修改/conf/passwd可以改变系统的登录shell。

但是以initrd的数据在flash中按图索骥却查不到。暂时搁置。

值得注意的是负责挂载网页管理界面程序所用分区的启动脚本/etc/init.d/init-sp.sh,它把flash的分区/dev/mtdblock4(www)挂载到/usr/local/www

1
2
3
dd if=/dev/$wwwmtdblock of=/dev/ram2
/usr/local/bin/dencryption /dev/ram2 /dev/$wwwmtd
mount -t cramfs /dev/ram2 /usr/local/www

这里用到了程序/usr/local/bin/dencryption,它从第二个参数中获取长度,使用ECB模式的Tiny Encryption Algorithm原地解密第一个参数(此处为/dev/ram2)。程序中编码了密钥,而在flash的u-boot bootloader区域也能找到该密钥,发现initrd也是被同样方式解密后挂载的,而执行这一解密过程的只能是u-boot。猜测开发人员修改u-boot,使用了Tiny Encryption Algorithm解密initrd传递给kernel。

提取flash中0x150000到0xf10000处内容,用dencryption解密算法得到u-boot legacy image,tail -c+65即可得到initrd,和/dev/ramdisk一致。

交叉编译工具链

1
2
3
4
# uname -a
Linux 6C92BF0BA1B6 2.6.28.10-ami #1 Thu Jan 8 17:50:26 CST 2015 armv5tejl unknown
# head -n1 /proc/meminfo
MemTotal: 215668 kB

安装crosstool-ng,执行ct-ng menuconfig配置交叉编译工具链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Paths and misc options --->
[*] Debug crosstool-NG
[ ] Pause between every steps
[*] Save intermediate steps
[*] gzip saved states
[*] Interactive shell on failed commands
Target options --->
Target Architecture (arm) --->
() Suffix to the arch-part
*** Generic target options ***
[ ] Build a multilib toolchain (READ HELP!!!)
[*] Use the MMU
Endianness: (Little endian) --->
Bitness: (32-bit) --->
*** Target optimisations ***
(armv5te) Architecture level
(arm926ej-s) Emit assembly for CPU
() Tune for CPU
() Use specific FPU
Floating point: (software (no FPU)) --->
() Target CFLAGS
() Target LDFLAGS
*** arm other options ***
Default instruction set mode (arm) --->
[ ] Use Thumb-interworking (READ HELP)
-*- Use EABI

gcc、glibc、binutils等有复杂的版本依赖关系,近年gcc make sed又都升级了主版本号,如果选择与BMC系统相近的glibc-2.11,./configure时会报错。方便起见glibc选择新版本,这样某些程序会因为依赖高版本符号而无法在旧glibc系统上执行,暂且不管。为了调试方便把strace和ltrace也选上。

ct-ng build。失败后可以用ct-ng list-stepsct-ng libc+等,避免前功尽弃。我在tmpfs下编译,完成后占用了近7GiB。

其他

内核启动参数bigphysarea=6144用于分配设置连续的物理内存,/proc/bigphysarea/proc/iomem可以看到地址的使用情况。

很多服务如kvm、虚拟磁盘等都用到了service location protocol,读取SLP配置文件,有待了解。

/usr/local/bin/IPMIMain负责处理system/LAN等interface的IPMI请求。Web管理界面和原来的shell都经过自行实现的libipmi.so,请求最终发往IPMIMain监听的unix socket。很多符号名和https://www.virustotal.com/en/file/e72785167675ab46c8abd7bc29e66488d78bc5c9ac494c8ca4abcfd5ae94e0e0/analysis/相同。

默认开启snmpd,但Web管理界面不显示。可以用snmpwalk -u $username -l authPriv -a MD5 -x DES -A $password -X $passsword $ip获取MIB树信息。

IPMI SDR数据取自/dev/{adc0,gpio0,i2c2,netmon,pwmtach0}等。

调试IPMIMain时需要关闭/etc/init.d中的watchdogapp以避免服务不可用导致系统自动重启。

参考资料