
This router is equipped with 8 MB of flash memory and 64 MB RAM. Its core is the MediaTek MT7620A chip. This chip is based on the MIPS architecture and is supported by the Linux kernel. The stock firmware is flushed with U-Boot. The built-in TFTP client can be used for recovery if you accidentally load a faulty firmware. The main thing is not to kill the bootloader; otherwise, your last resort would be a soldering iron.
In my opinion, this piece of hardware is perfectly suited for experiments. One of its downsides is that DIR-806A doesn’t have USB out-of-the-box. However, USB wiring is present on the board, and if you possess the proper knowledge and skill, you can solder this port on your own.
Let’s connect to UART first; make sure that you have a level converter for UART. During the connection, don’t forget to ‘criss-cross’ RX and TX: the RX line must be connected to TX; while TX, to RX. Connection parameters: 57600 8N1.

Congrats, you’ve connected to UART. Now let’s try to flush the patient keeping an eye on the console. Perhaps, some lines there could help you find the code sections responsible for the flushing process?
signallin(6) start... mtd: "Linux"libmtd (_mtd_write_ex): to "/dev/mtd6", size: 0x6c4764, offset: 0x0, buffer: 0x2afa6000
Success! The router displays a message in the console that the update has started and indicates the section the firmware is loaded to (Linux
). This is a classical reversal engineering procedure: you search for certain lines, and based on these lines, identify specific locations in the code. Let’s search for the line start..
in the firmware:
# grep -nr "start..." /sbin/sbin/fw_updater:12411:(%d) start... mtd: "%s"
Here is the fw_updater
utility. Let’s run it:
fw_updater
(6) usage: fwupdater
Apparently it’s fw_updater
that loads the new firmware to the Linux
section.
info
If there were no text, you would have no choice but to reverse the web interface. It always contains messages, error codes, etc., and you can search for them afterwards. Based on the web interface, you can also find firmware utilities. Or you can randomly search for utilities whose names contain such words as fw
, firmware
, and update
. Alternatively, you can search the firmware for such strings as CRC
, image
, etc.
Now let’s try to find in the firmware the code section from where fw_updater
is called:
grep -nr “fw_updater” /lib
/lib/libdhal.so:88250:/sbin/fw_updater
/lib/libdhal.so:88254:/tmp/fw_updater
As you can see, fw_updater
is used in the libdhal.
library. Interesting… Let’s see what’s inside it. I suggest to use Ghidra for reverse engineering since it can convert a binary executable file into C code. This is very convenient, and you don’t have to dig into the assembler listing.
You also need a utility that can transfer the required file to you computer. Any network file transfer mechanism would fit. If there is nothing suitable on the router, you can unpack the firmware on your computer using binwalk. Note that there are no universal solutions suitable for all situations…
In this particular case, netcat is available. Let’s transfer libdhal.
to the computer:
cat /lib/libdhal.so | nc 10.0.0.245 5000
Next, let’s create a project in Ghidra and disassemble the library. A quick examination shows that it contains a function with a self-explanatory name: check_firmware_in_buffer
. Below is its listing in C:
undefined4 check_firmware_in_buffer(int param_1,int param_2,undefined4 param_3,undefined4 param_4){ uint uVar1; ulong uVar2; int iVar3; char *pcVar4; char local_98; char local_97; undefined local_96; undefined local_95 [16]; undefined auStack_85 [17]; undefined auStack_74 [92]; logmessage("check_firmware","Check signature in the firmware",param_3,param_4); if (param_2 < 0x80) { pcVar4 = "Too small fw"; } else { uVar1 = *(uint *)(param_1 + param_2 + -4); if ((uVar1 >> 0x18 | uVar1 >> 8 & 0xff00 | uVar1 << 0x18 | (uVar1 & 0xff00) << 8) == 0xc0ffee) { local_96 = 0; pcVar4 = "cef285a2e29e40b2baab31277d44298b"; do { local_97 = pcVar4[1]; local_98 = *pcVar4; uVar2 = strtoul(&local_98,(char **)0x0,0x10); local_95[(uint)(pcVar4 + -0x83218) >> 1] = (char)uVar2; pcVar4 = pcVar4 + 2; } while (pcVar4 != ""); md5_init(auStack_74); md5_append(auStack_74,local_95,0x10); md5_append(auStack_74,param_1,param_2 + -0x14); md5_finish(auStack_74,auStack_85); param_3 = 0x10; iVar3 = memcmp((void *)(param_1 + param_2 + -0x14),auStack_85,0x10); if (iVar3 == 0) { logmessage("check_firmware","Signature OK!",param_3,param_4); return 2; } pcVar4 = "Wrong signature!"; } else { pcVar4 = "Wrong magic or version"; } } logmessage("check_firmware",pcVar4,param_3,param_4); return 0;}
This function makes the decision whether a particular firmware is suitable for the router or not. Three parameters are checked: image size, magic number, and MD5 hash.
A brief analysis of the above code makes it possible to conclude that:
-
param_1
is a pointer to the buffer containing the firmware file; and -
param_2
is the buffer size.
Look at the string below:
uVar1 = *(uint *)(param_1 + param_2 + -4);
It’s obvious that the magic number is contained in the last four bytes of the firmware and is equal to 0xc0ffee
.
Time to examine the hash:
md5_append(auStack_74,local_95,0x10);md5_append(auStack_74,param_1,param_2 + -0x14);
As you can see, the hash is computed based on the local_95
array 16 bytes in size and the firmware file (except for the 20 bytes at the end). These 20 bytes are the size of MD5 + the 4 bytes of the magic number. The local_95
array is built on the basis of the pcVar4
parameter that contains the device UUID.
Let’s see what’s going on in this piece of code:
local_98 = *pcVar4;uVar2 = strtoul(&local_98,(char **)0x0,0x10);local_95[(uint)(pcVar4 + -0x83218) >> 1] = (char)uVar2;
This is a conversion operation: a byte is converted from textual representation to machine code.
Time to check your coding skills. Let’s write a Python script to compute MD5:
import sys, os, hashlibsize = os.path.getsize(sys.argv[1])with open(sys.argv[1], "rb") as f: data = f.read(size - 20)hash_md5 = hashlib.md5()hash_md5.update(bytes.fromhex("cef285a2e29e40b2baab31277d44298b"))hash_md5.update(data)print(hash_md5.hexdigest())
The above script takes firmware as input. Its execution result is shown below:
user@debian:~/md5$ python3 md5.py 2019.03.19-18.04_DIR_806A_MT7620A_3.0.1_release.bine5fd006108c91a7fd4e43b23575fa7cd
Compare the computed hash with the original firmware at offset 0x6c4750
, and you’ll see that the hash was computed correctly.

Now you can easily create firmware that can be flushed using the stock web interface.
MD5 hashes (especially those composed of UUID and image, as in this particular case) are pretty rare. The specific implementation depends only on the manufacturer’s creative imagination. For example, such vendors as SNR or Keenetic prefer simpler solutions. SNR engineers, instead of computing the CRC32 checksum of the kernel, compute CRC32 of the entire firmware; while Keenetic guys specify the magic number, CRC32, and device ID in the firmware.
In terms of complexity, the situation with firmware embedded into Xiaomi routers is similar. They also operate with minimum firmware size and magic number, but instead of MD5, RSA is used… Overall conclusion: there is no signature that could not be reversed.
Good luck!

2022.06.01 — Routing nightmare. How to pentest OSPF and EIGRP dynamic routing protocols
The magic and charm of dynamic routing protocols can be deceptive: admins trust them implicitly and often forget to properly configure security systems embedded in these protocols. In this…
Full article →
2022.06.01 — Quarrel on the heap. Heap exploitation on a vulnerable SOAP server in Linux
This paper discusses a challenging CTF-like task. Your goal is to get remote code execution on a SOAP server. All exploitation primitives are involved with…
Full article →
2022.02.09 — First contact: An introduction to credit card security
I bet you have several cards issued by international payment systems (e.g. Visa or MasterCard) in your wallet. Do you know what algorithms are…
Full article →
2023.06.08 — Cold boot attack. Dumping RAM with a USB flash drive
Even if you take efforts to protect the safety of your data, don't attach sheets with passwords to the monitor, encrypt your hard drive, and always lock your…
Full article →
2022.01.11 — Pentest in your own way. How to create a new testing methodology using OSCP and Hack The Box machines
Each aspiring pentester or information security enthusiast wants to advance at some point from reading exciting write-ups to practical tasks. How to do this in the best way…
Full article →
2022.01.12 — Post-quantum VPN. Understanding quantum computers and installing OpenVPN to protect them against future threats
Quantum computers have been widely discussed since the 1980s. Even though very few people have dealt with them by now, such devices steadily…
Full article →
2022.02.09 — Kernel exploitation for newbies: from compilation to privilege escalation
Theory is nothing without practice. Today, I will explain the nature of Linux kernel vulnerabilities and will shown how to exploit them. Get ready for an exciting journey:…
Full article →
2022.06.02 — Blindfold game. Manage your Android smartphone via ABD
One day I encountered a technical issue: I had to put a phone connected to a single-board Raspberry Pi computer into the USB-tethering mode on boot. To do this,…
Full article →
2023.03.03 — Nightmare Spoofing. Evil Twin attack over dynamic routing
Attacks on dynamic routing domains can wreak havoc on the network since they disrupt the routing process. In this article, I am going to present my own…
Full article →
2023.02.13 — First Contact: Attacks on Google Pay, Samsung Pay, and Apple Pay
Electronic wallets, such as Google Pay, Samsung Pay, and Apple Pay, are considered the most advanced and secure payment tools. However, these systems are also…
Full article →