Ed
banner
deinonychus.bsky.social
Ed
@deinonychus.bsky.social
chicken appreciator // breaking computers for fun and profit // 20s
that one gif.. you know the one lol
December 4, 2025 at 5:09 AM
We then setup a vlan on the ethernet port. The vlan ID AT&T is using appears to be 2762, but you can figure out what it should be by looking in wireshark or inspecting the vtfd MIB. The modem doesn't strip vlan encapsulation, so we need this setup to get rid of it.
December 4, 2025 at 3:12 AM
Reboot the ONT, plug it into the fiber line, and check that it gets O5 (operational) status on the network. We can inspect /tmp/omci.log or use omcli parser getonustatus.
December 4, 2025 at 3:12 AM
We now set the rest of the values we need to spoof in the RI store. These values are set to mimic the BGW320 terminal AT&T installed. The clei code and mnemonic together (without dashes) form the equipment ID checked by the OLT.
December 4, 2025 at 3:12 AM
Now we flash the new firmware slot from our TFTP server. Nerve-wracking, but if we screw up we should be able to recover with the flash dump we took earlier. If we *really* screw up though, we'll have to break out the hot air gun and pull off the flash. So let's try not to do that.
December 4, 2025 at 3:12 AM
We can now build firmware packages to flash to the device. We invoke the modified image builder with the IDs we found earlier, the dumped kernel image, and critically - the patched squashfs we built earlier. We now have a modified firmware package we can flash!
December 4, 2025 at 3:12 AM
Now that we understand the header, we can also pull the kernel from the flash. We take kernelAddress from the firmware slot header, subtract 0xbfc00000 from it (the flash base) to get a flash offset of 0xCE6100. Then we read kernelLen bytes at that location. We save that off as kernel.img.
December 4, 2025 at 3:12 AM
Each firmware slot starts with a 256-byte header. Then the squashfs file system follows, and finally the kernel image.
December 4, 2025 at 3:12 AM
We can also just use binwalk on the full flash dump and it picks up on the CFE payload immediately. It also catches one of the firmware slots at 0x20000 and a second firmware slot at 0x750000. CFE looks at both of these slots and boots the one with the higher sequence number.
December 4, 2025 at 3:12 AM
Eventually we find the routine responsible for loading and decompressing the second stage of CFE from SPI flash. Doesn't look like it's authenticated, which is a good sign. This routine gives us important context about how the second stage is stored on flash.
December 4, 2025 at 3:12 AM
The bootrom is weird. It resides at flash offset 0 but is linked at 930A0000. In memory it technically executes at 0xbfc00000 while in KSEG1. It makes itself position independent by storing the difference between the linked base addr and its actual location (found with jalr and reading $ra) in $s6.
December 4, 2025 at 3:12 AM
Finally, we run the dump command and wait for those last sectors to transfer. So now our flash is backed up and we have a copy of the kernel and the actual version of CFE running on the device. We can now begin to look into how firmware might be loaded by CFE. Let's start with the bootrom.
December 4, 2025 at 3:12 AM
We boot our custom build of CFE off a TFTP server on the host. Our image needs to be formatted as a lzma compressed blob with some custom headers. Uncompressed ELF loading seemed to just error out in this CFE build.
December 4, 2025 at 3:12 AM
In the end we reuse CFE's TFTP machinery to connect to our host, and then read out the flash sector by sector into a dump on the TFTP server. Pretty cool.
December 4, 2025 at 3:12 AM
I sure as heck am not waiting for something like this to complete over a 115kbit serial connection though. And unfortunately, there's a more pressing issue. Only the first 64k of flash is mapped into memory. After that we crash with a bus read fault. So we need a better way.
December 4, 2025 at 3:12 AM
unsquash a fresh copy of the mtd0 dump from earlier, copy the new omciMgr in, and then rebuild the squash image, taking care to use the same compression and block size parameters. Easy. What's next?
December 4, 2025 at 3:12 AM
Perfect. That was much simpler to patch. Assuming everything goes well, we can proceed to pack this modified binary into a new squashfs image. Then we have to figure out how to flash it.
December 4, 2025 at 3:12 AM
We repeat the process with the call to configUniDot1xParams here, replacing it with a nop as well.
December 4, 2025 at 3:12 AM
Poke all those bytes into the binary with a hex editor, and then we'll open it back up in IDA and double check that everything is as we expect. The three blocks we patched are now all fused together, version string looks correct, and the strcpy jump is always taken. Sweet!
December 4, 2025 at 3:12 AM
We only really need to assemble two instructions, the 'sw' and the 'j'. nop in MIPS is just 0x00000000, so that's easy. We do this with keystone. We ignore the trailing zeros on the j instruction, that's just keystone assembling a dummy instruction for the jump's delay slot.
December 4, 2025 at 3:12 AM
First things first. The patches. The strategy here is basically to force the graph towards that strcpy call by patching branches. This way it'll load a hardcoded identifier from the binary.
December 4, 2025 at 3:12 AM
The parser ships OMCI messages back over this queue. Each message is handled by a massive switch statement in a function I've named OmciHandleMessage.
December 4, 2025 at 3:12 AM
The SW version comes from getSwVerInfo, which calls swdl_get_version in libuprade, which makes an indirect call to a vtable in libupgradeimpl, which then makes an ioctl call to the kernel chardev /dev/brcmboard. At this point I don't even have a kernel image so that's a dead end.
December 4, 2025 at 3:12 AM
Load omciMgr into IDA and do some RE. We've got symbols at least. Looks like most of the ONT identifiers we care about are derived from the RI store we found earlier and can already spoof with 'ritool set'. Unfortunately for us there's one value we can't.
December 4, 2025 at 3:12 AM
Let's dump the rootfs and data partitions and take a closer look at em. The strategy here is to cat out the raw mtd devices and use nc to get them onto the host computer. On the host computer we use unsquashfs to extract the rootfs.
December 4, 2025 at 3:12 AM