Pwning the bcm61650

published on Mon 04 April 2022 by

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.

This entry was tagged #Reverse

Social Network

Categories

Feeds