Pwning the bcm61650
published on Mon 04 April 2022 by Xilokar
The percello prc6000, also known as bcm61650 after Broadcom bought the company, is a chip used in 3g femtocells (Home-nodeB).
Here is a summary writeup of how I achieved to bypass its secure ROM to run arbitrary firmwares.
Intro
The bcm61650 is present in a femtocell of a large french operator. It allows you to have a small 3g tower in your house over your dsl/fiber line. It's an optional box you plug in your dsl box, with what looks like a pcie connector.
From PCIe in box to standalone Ethernet
When pluged in, the board present itself as a pci ethernet chip, that performs a bootp and load its firmware over tftp and then connects to an hardcoded IP to establish an ipsec tunnel to the core network (I won't cover this part). In order to fully use the femtocell for playing with osmocom, I decided to try get rid of the hardcoded parts.
To ease debugging, I decided to try to tweak it into a standalone, ethernet based, board, in order to be able to start, stop or reboot the board without having to mess with pci scanning and hotplug.
Desoldering the link between the pci ethernet chip and the bcm61650
And with a few probes, managing to get the board powered and booting.
With uart output.
Node-H FM-loader v1.2 booting in ******* mode changing CPU frequency... done image verification success copying initrd at 0X8F6C8000... copying kernel... bytes: 3338752 booting kernel...
Hum... image verification
First glimpses
The firmware loaded over tftp is 11Mb.
binwalk output:
DECIMAL HEXADECIMAL DESCRIPTION --------------------------------------------------------------------- 123953 0x1E431 Copyright string: "Copyright (c) MIPS Technologies, Inc. All rights reserved. 125092 0x1E8A4 CRC32 polynomial table, little endian 126181 0x1ECE5 Copyright string: "Copyright (c) MIPS Technologies, Inc. All rights reserved. 128416 0x1F5A0 SHA256 hash constants, little endian 131224 0x20098 uImage header, header size: 64 bytes, header CRC: 0x8ACC835E, image name: "Linux-2.6.31.12-Percello-1.12-9" 131288 0x200D8 gzip compressed data, has original file name: "vmlinux.bin" 1740800 0x1A9000 Squashfs filesystem
So, a bunch of unkown stuff at the start, then kernel and root filesystem.
Trying a simple change in the kernel/rootfs:
Node-H FM-loader v1.2 booting in ******* mode changing CPU frequency... done image verification failed failed to boot: halt
Yeah, obviously
Some string a the start of the binary blob:
Percello bootloader version 0.6 (from Silicon type: , silicon ver: , feature ver: , ROM ver: Boot block not found FM signature has invalid length
Did not see the Percello bootloader version 0.6 during boot. And the FM signature suggests we surely have a chain of bootloaders until linux (which is expected).
Trying to change a string in the bootloader completly blocks the boot, so bootloaders are either checksumed or a signature is checked.
Where to go from now ?
Got root ?
Extract initrd with binwalk, check /etc/password
Long story short: empty root password.
side-note: from now on, I will always check for empty password before trying to build an hashcat rule
Got Dump ?
dd if=/dev/mem bs=4k | nc
With luck, the memory used during boot time will not (yet) be overwritten by linux.
Using the strings in the firmware and in the location of the matchings strings in the memory dump, we can find the location in ram of the bootloader, and thus adjust the load offset of the binary in our favorite RE tool.
After a few hours of digging things out:
- The percello bootloader is loaded at 0x83fe0000 It then loads the FM loader
- The FM loader is loaded at 0x83f80000 he is responsible for loading kernel and initrd
Both use code from ROM (0x9fc00xxx), for signature verification.
Time to go further down in our analysis.
Got Rom ?
The dd approach to dump rom does not seems to work:
# dd if=/dev/mem bs=1 skip=$((0x9fc00000)) count=10 dd: /dev/mem: Bad address #
Not a surprise, the rom is surely not mapped in linux. But hey, we are root, and we can load modules!
So grab a linux-2.6.31.12 archive, try to build a close enough kernel (did I mentionned it was mips based ?) play with symbol versioning and voila
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
void dump_mem(unsigned char *start, unsigned char *end) {
unsigned char *p = start;
int i,v;
printk("Dumping: %08x\n", (unsigned int)start);
v = p[i];
for(i=0; i< (int)(end - start);i++) {
printk("DUMP:%08x: %02x\n",
i + (unsigned int)start, p[i]);
}
}
static int __init dump_init(void)
{
printk("Dump init\n");
// dump rom ?
dump_mem((unsigned char*)0x9fc00000,
(unsigned char*)0x9fd00000);
return 0;
}
static void __exit dump_exit(void)
{
printk("Dump exit\n");
}
module_init(dump_init);
module_exit(dump_exit);
MODULE_AUTHOR("Xilokar");
MODULE_DESCRIPTION("Dump driver");
MODULE_LICENSE("GPL");
(do not pay attention to code)
grab kernel messages from console, grep, sed, xxd and we have our ROM dump.
more time digging things out
Hello RSA my old friend
Remember the functions from ROM used in both percello bootloader and FM-loader ? Without surprise, they are also used in the ROM code, to check the percello bootloader signature.
log("s_Secured_device_-_OTP2:_verificat_9fc0dda8");
iVar1 = FUN_9fc0d15c(DAT_bf400888,DAT_bf400880,
&DAT_bf400ae8,&DAT_bf40088c);
if (iVar1 != 0) {
log("s_FAILED_9fc0e364");
return;
}
void FUN_9fc0d15c(undefined4 param_1,undefined4 param_2,
undefined4 param_3,undefined4 param_4)
{
undefined auStack152 [20];
undefined auStack132 [108];
FUN_9fc0c380(auStack132);
FUN_9fc0c96c(auStack132,param_1,param_2);
FUN_9fc0d020(auStack132,auStack152);
FUN_9fc0ccc0(auStack152,param_3,param_4);
return;
}
Probably, FUN_9fc0c380 is hash init, FUN_9fc0c96c hash process and FUN_9fc0d020 hash finalize. The hash size is 160,
void FUN_9fc0c380(undefined4 *param_1)
{
*param_1 = 0x67452301;
param_1[1] = 0xefcdab89;
param_1[2] = 0x98badcfe;
param_1[3] = 0x10325476;
param_1[4] = 0xc3d2e1f0;
param_1[0x19] = 0;
param_1[5] = 0;
param_1[6] = 0;
param_1[0x17] = 0;
param_1[0x18] = 0;
return;
}
And we have good old Sha constants, so hash is surely sha1
The last function (FUN_9fc0ccc0) builds a 0x80 long structure
00000000 00 01 ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00000010 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff * 00000050 ff ff ff ff ff ff ff ff ff ff ff ff 00 30 21 30 00000060 09 06 05 2b 0e 03 02 1a 05 00 04 14 00 00 00 00 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
and copy the sha1 at offset 0x6c. (yes PKCS1 scheme).
It then rsa-decode the passed 0x80 long signature with an hardcoded public key and memcompare the result to the built structure. No asn1 parsing of the provided signature, so no chance to find a pkcs bleichenbacher like attack...
Back to basis
At this point, It was like a deja vu:
(sha1 this time, but hey, even if sha1 collision are real, not preimage attacks)
But after more digging:
void probably_load_header(void)
{
bootloader_header local_60;
DAT_bf400888 = 0xa0500000;
memcpy(&local_60,0xa0500000,0x4c);
memcpy(&DAT_bf40088c,local_60.percello_sig + 0xa0500000,
local_60.percello_sg_len);
memcpy(&DAT_bf40090c,local_60.fm_sig + 0xa0500000,
local_60.fm_sig_len);
DAT_bf400880 = local_60.percello_sig;
DAT_bf400884 = local_60.fm_sig;
return;
}
0xa0500000 is probably the location of the tftp-loaded image. So this function maps the file header and...
Wait ! memcpy a user provided buffer (fm_sig at a known location (0xbf40090c), with a user provided length (fm_sig_len)?
And below the stack (0xbf403ff0) ?
# init stack pointer
lui t0,0xbf40
ori t0,t0,0x3ff0
addiu sp,t0,0x0
Time for a good old stack smashing (for fun and profit)!
After a few time, found the right len to smash ra in the memcpy call on the stack, and jump just after the signature verification. Also needed to keep some values in ram, so my buffer overflow was not only full of 0xff and I was able to successfully jump to the percello bootloader, without signature verification.
Node-H FM-loader v1.2 booting in ******* mode changing CPU frequency... done image verification bypass! copying initrd at 0X8F6C8000...
Then all was left was patching percello bootloader to skip FM-loader verification:
Node-H FM-loader v1.2 booting in Xilokar mode changing CPU frequency... done image verification success copying initrd at 0X8F6C8000... copying kernel... bytes: 3338752
And then patching FM-loader to allow for arbitrary kernels and initrd:
Node-H FM-loader v1.2 booting in Xilokar mode changing CPU frequency... done image verification bypass! copying initrd at 0X8F6C8000... copying kernel... bytes: 3338752 booting kernel... [ 0.000000] Linux version 2.6.31.12-Percello-1.12-9 ... [ 1.633762] Freeing unused kernel memory: 152k freed ================================================================== Welcome to Percello Root File System Version R2.4 (not so) Secure Pwned by Xilokar ================================================================== ---- Mounting proc file system Sat Aug 1 00:00:00 UTC 2015 ---- Mounting ramdisk with size 32M ---- Remounting /dev/root ---- Starting syslogd ---- Mounting sysfs ---- Mounting devpts ---- Creating loopback device ---- Activating Ethernet device
(I realize I did not talk about understanding firmware image structure)
Conclusion
Unpatchable (ROM) secure boot bypass on bcm61650.