D-Link DIR-882 R1 Bootloader FW Image Verification

I have purchased a D-Link DIR-882 in the hopes of installing OpenWRT only to have realized after opening it at it is the unsupported Russian model.

The Russian model firmware contains a telnet server that can be enabled. I used it to dump all of the /dev/mtdX devices onto a USB flash drive. I also checked the flash layout and it matched the layout described on the DIR-882 A1 page:

/proc/mtd
dev: size erasesize name
mtd0: 01000000 00010000 "ALL"
mtd1: 00030000 00010000 "Bootloader"
mtd2: 00010000 00010000 "Config"
mtd3: 00010000 00010000 "Factory"
mtd4: 001b0000 00010000 "Kernel"
mtd5: 00e00000 00010000 "RootFS"
mtd6: 00fb0000 00010000 "Linux"

I also saw that there's a tool called fw_updater that takes a name of a partition and a image to flash the MTD directly. I verified that mtd6 is the same as mtd4 and mtd5 concatenated together.

I gambled and tried using this tool to install the OpenWRT factory image to "Linux" (mtd6). My new brick now only boots into the dlink recovery GUI. The recovery GUI is not the same as the US recovery GUI, and refuses to take any images with the error message "ERROR - the image you uploaded failed to pass verification.", regardless of if it is from the official site (http://ftp.dlink.ru/pub/Router/DIR-882/Firmware/), the US D-Link site, or any openwrt firmware.

I have contacted D-Link for the source code of their bootloader, as well as firmware images. I am wondering if I can reverse engineering the bootloader verification to understand how it determines whether the images are valid to unbrick my router. The bootloader image (mtd1) contains this string and many more. Is there any guide available as to how to go about starting to disassemble it? binwalk indicates it is based on U-Boot 1.1.3, and it has strings that indicate that it contains the uIP networking TCP/IP stack.

I know I also can solder a serial cable onto the hardware pins to get the router into TFTP recovery mode, but I have more MIPS experience than soldering experience and I'd rather avoid definitively voiding the warranty (if I haven't already yet). I also know this is probably what I'll end up having to do.

the Russian version has a different boot loader header size & partition layout
you will find there is a extra config 64K partition on the A1 models after the factory 64K partition
they are shown on the A1 openwrt versions as a 128K Factory partition
if you wish make your own image I can give you a start
I have installed the Russian boot loader & firmware on my A1 model
but as I don't have a real R1 Model I can't really test as an R1

Did you try tftp client recovery? Some bootloaders start both httpd and tftpd daemons in recovery mode.
BTW Can you share your dumps?

Thanks for the replies.

Lucky1 - I just rechecked the mtd layout and I see that you're right - there's an extra 64 kB utilized by the A1 model.

I managed to get the latest factory firmware from dlink and loaded it on the recovery gui. I also managed to extract the old firmware dump from the dumps of the mtd I took.

I ran binwalk on the binary dlink gave me, and noticed there were an extra 56 bytes after the end of the squashfs partition, with two obvious magic numbers - 0x00c0ffee. I also noticed a similar 51 bytes after the end of the squashfs image on the mtd dump that I took with the same two magic numbers. When I truncated my dump that the second magic coffee, my image loaded just fine in the recovery GUI.

The hex dumps of these last bytes are below:

Original firmware:

00000000: 3030 312e 3030 312e 3030 3032 443c 69fa  001.001.0002D<i.
00000010: a019 a045 8ce5 3a2a 21a9 0300 c0ff eea3  ...E..:*!.......
00000020: 972a a725 12d5 72a9 2f1b 2c89 dda4 c600  .*.%..r./.,.....
00000030: c0ff ee                                  ...

Updated firmware:

00000000: 4441 5441 4d32 2e34 312e 3044 4154 414d  DATAM2.41.0DATAM
00000010: 08f4 dc30 8811 047e 18a3 df3c 7c27 ad10  ...0...~...<|'..
00000020: 00c0 ffee 8ae5 b109 021f 5d46 eb8e c12a  ..........]F...*
00000030: d0b6 2811 00c0 ffee                      ..(.....

The firmware images on the Russian ftp site only have one magic 0x00c0ffee:

00000000: 3030 312e 3030 312e 3030 30d5 e969 36df  001.001.000..i6.
00000010: e384 6c9f 6994 f6e2 56ba 2500 c0ff ee    ..l.i...V.%....

It seems that the first part of the trailing bytes seems to be some sort of variable width ascii printable information (perhaps versioning) following by 16 bytes of data and then 0x00c0ffee. The second part of the tail seems to be 16 bytes of data followed by 0x00c0ffee.

I started reverse engineering the mtd1 partition with Ghidra, and found the following relevant part of the mtd1 code:

            if (!bVar1) {
              iVar23 = *(int *)(*ppcVar14 + 0x10);
              (*DAT_0001b494)(&local_34,iVar23 + -0x7fc00018,4);
              (*DAT_0001b494)(&local_38,iVar23 + -0x7fc00004,4);
              pcVar28 = (code *)(&DAT_000060e0 + DAT_0001b564);
              iVar17 = (*pcVar28)(local_34);
              if ((iVar17 == 0xc0ffee) && (iVar17 = (*pcVar28)(local_38), iVar17 == 0xc0ffee)) {

I'm a bit stuck here as the function calls here don't have any obvious destinations (although it may well just be that this is my first try at using Ghidra and I don't know what I'm doing).

I might also be going about this wrong - perhaps there's a way to build an OpenWRT image that can be flashed though the fw_updater binary on telnet.

123serge123 - I am happy to share my dumps - please let me know where I can upload them. Is it ok to omit the config partitions as I assume that might assume data specific to my router?

I tried tftp recovery too and it seems that the recovery firmware only has an http server. I might be mistaken here too though.

Here is a modifyed version for you to try with the R1
http://luckys.onmypc.net/openwrt/DIR-882-R1/
Factory Partition changed to 64k from 128K
moved Firmware partition to 0x50000 from 0x60000
Firmware partitions size changed to add extra 64K
change firmware partition type to "denx,uimage" from "sge,uimage"
remove header padding off image creation "uimage-padhdr 96"
change labeling etc

Lucky1 - which image should I take to flash with fw_updater?

I think these byte are just the end of the d-link firmware files
the only firmware magic is the start 27051956
the R1 used the same basic header is as others
the A1 had a custom bigger header with an extra 96 bytes

that image is just based off the current snap shot
last time I checked a few day's go the WiFi didn't work well at all

from the recover interface use the factory image
oh it's called "Failsafe UI" in this model

I loaded the factory image with the fw_updater binary on the router, and OpenWRT booted. By any chance might you be able to share the build configuration exactly so I can make a 19.4 build for my router?

I didn't try loading this through the Failsafe UI, because it seems to reject anything that doesn't have the right signature at the end.

I'm interested in getting a copy of the U-Boot-env info for that unit
if i load the Russian firmware it has an error about WPS button location
I think it may be in there
but yes remove any info you think is personal
mine has the following by default

bootcmd=tftp
bootdelay=1
baudrate=57600
ethaddr="00:AA:BB:CC:DD:10"
ipaddr=192.168.0.1
serverip=192.168.0.101
BootType=3
init=/bin/sh
stdin=serial
stdout=serial
stderr=serial
rdinit=/bin/sh.
din=serial
stdout=serial
stderr=serial

Lucky1 - I am happy to send my dump of mtd1 over to you - just let me know where to upload it. I did a sanity check of it and I didn't find anything personal in it.

it should be just text like above if you can just paste it here

if you have a serial interface you would get it by "printenv"

I don't have anywhere to upload a file atm

oh btw the only check in "Failsafe UI" is the image magic valid header & crc32 check

I don't have a serial console to the router. I only have the image of the boot loader partition. Perhaps I can e-mail it or private message it in base64 (it's less than 64 kB compressed with xz). Is there a way I can get the information you're asking for from the OpenWRT logs?

I do see the following looking for strings in the mtd1 dump:

bootcmd=tftp
bootdelay=5
baudrate=57600
ethaddr="00:AA:BB:CC:DD:10"
ipaddr=192.168.0.1
serverip=192.168.0.180

I realize that in other versions of the router that no other checks are done for firmware image validation. But these checks definitely are done on the bootloader that came with my router. Changing the bytes near the 0x00c0ffee tail causes it to reject the firmware image.

yes any change within the image outline in the header will make the crc32 fail

you can send it to this disposable address
user@luckys.onmypc.net

Just sent it over to you.

Are you sure the signature is at the end? As Lucky1 said and from what I have seen the fimrware check is normally done at the uboot header.

I had a quick look at the image from the russian ftp website and and the check should be done between 20 and 2F.

This is the uboot header

0000:0000 | 27 05 19 56  27 BC 0E C3  5C 94 B4 64  00 1A 25 9F | '..V'¼.Ã\.´d..%.
0000:0010 | 80 00 10 00  80 3A 44 30  11 2C 86 0A  05 05 02 03 | .....:D0.,......
0000:0020 | 44 49 52 5F  38 38 32 5F  4D 54 37 36  32 31 41 54 | DIR_882_MT7621AT
0000:0030 | 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00 | ................

If I am not wrong any image with the correct CRC that is easy to calculate (thanks to lucky1's help) and the correct image name ( DIR_882_MT7621AT) would be accepted and written to flash.

This is Lucky's image where I only changed the header to match the factory one you use (with a correct CRC). If you want to give it a go and try and load it from the recovery. I did not touch anything else by the way.

Hi kar200 - thanks for the response. I tried your image, and it too was rejected by the Failsafe UI.

I am not certain the signature is at the end, but it certainly looks this way. None of the US firmware images that I checked have any trailing bytes after the squash fs image, but the Russian ones do:

$ binwalk 2018.11.20-15.53_DIR_882_MT7621AT_3.5.1_release.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             uImage header, header size: 64 bytes, header CRC: 0x88386E79, created: 2018-11-20 13:14:42, image size: 1720670 bytes, Data Address: 0x80001000, Entry Point: 0x803A9250, data CRC: 0xFD434DD8, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "DIR_882_MT7621AT"
64            0x40            LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 5040576 bytes
1769472       0x1B0000        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 9294372 bytes, 2449 inodes, blocksize: 524288 bytes, created: 2018-11-20 13:14:36

$ ls -asl 2018.11.20-15.53_DIR_882_MT7621AT_3.5.1_release.bin
10808 -rw-r--r-- 1 pyckle pyckle 11063875 Jan 1 00:00 2018.11.20-15.53_DIR_882_MT7621AT_3.5.1_release.bin

$ tail -c $(echo '11063875 - (1769472+9294372)'| bc) 2018.11.20-15.53_DIR_882_MT7621AT_3.5.1_release.bin | xxd
00000000: 3030 312e 3030 312e 3030 30d5 e969 36df  001.001.000..i6.
00000010: e384 6c9f 6994 f6e2 56ba 2500 c0ff ee    ..l.i...V.%....

Notice that the extra bytes end with a magic hex character 0x00c0ffee, and putting the bootloader image into Ghidra produces this magic number in code that is connected to the HTTP server. This is enough for me to believe that there is some sort of signature at the end.

Even the images from the Russian site are rejected from the Failsafe UI. The only images I have successfully passed through this bootloader are images with the double 0x00c0ffee checksum.

As I managed to flash my device, I don't see any urgency in reverse engineering how exactly it is calculated, but I may try to do this in the future, as it makes recovery flashing significantly easier. I do not have significant experience reverse engineering low level code so if somebody with more experience would like to guide me, I might have more luck.

My gut feeling is since both seem to have 16 bytes of binary gunk before the 0x00c0ffee magic word, it's some sort of hash, likely md5 based as md5 is 16 bytes.

That is interesting indeed. Do you mind sharing the image that works please?

Also have you tried a different web browser or curl? I am curious on how is this possible (that the official images are rejected as well. Have a look here for the curl command.

Thank you once again for your work on this - this build works perfectly. I even built in again on my own laptop for got measure and it works great! Please let me know if I can help get this into the mainline.