How I saved 20$/year in two days, or, how to hack Huawei WA1003A modem+router to do free dynamic DNS updates

In my last blog I discussed how I connected my Linksys WVC54GCA IP camera, and how cool that device is. I was not very amused with the fact that I had to shell out 20$/year to keep using the TZO dynamic DNS service that came bundled with the camera. As we saw, w/o this dynamic DNS update thing, the camera is not very usable.

Can we patch the Linksys IP camera  ?

TZO is one of the various dynamic DNS service providers like www.no-ip.comwww.dyndns.com and many others. In the past I had used the free no-ip client and it had always worked great for me, so I thought of using it instead of the paid TZO service. The catch here is that, I had used PC based no-ip client which runs as a daemon on your PC and keeps updating your IP address. I definitely do not want a PC to be always running just to do the dynamic DNS updates.  Since the TZO client is running inside the Linksys camera,  if we can push in a no-ip client instead, into it, we can get the same dynamic DNS update service for free. Lets see if we can get into the Linksys camera.

[root@cheetah ~]# telnet 192.168.2.100
Trying 192.168.2.100...
telnet: connect to address 192.168.2.100: Connection refused
[root@cheetah ~]# ssh root@192.168.2.100
ssh: connect to host 192.168.2.100 port 22: Connection refused
[root@cheetah ~]#

No luck! Lets do a portscan and OS fingerprinting to see the open ports and the possible OS running inside it. If it is not running Linux, its not worth my time.  Using nmap

[root@cheetah ~]# nmap -O 192.168.2.100

Starting Nmap 4.53 ( http://insecure.org ) at 2010-06-01 22:27 IST
Interesting ports on 192.168.2.100:
Not shown: 1712 closed ports
PORT    STATE SERVICE
80/tcp  open  http
554/tcp open  rtsp
MAC Address: 00:1D:7E:AD:30:56 (Cisco-Linksys)
Device type: general purpose
Running: Linux 2.4.X
OS details: Linux 2.4.18 - 2.4.32 (likely embedded)
Uptime: 0.011 days (since Tue Jun  1 22:12:10 2010)
Network Distance: 1 hop

OS detection performed. Please report any incorrect results at http://insecure.org/nmap/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.153 seconds

Ok, so it is indeed running Linux (good thing), but it does not seem to support login into the device (bad thing). We can explore ways to get into it, but let me for a moment check the other device that I’ve – my Huawei Quidway WA1003A ADSL modem. If that provides an easy entry, we can patch that instead and our job will be accomplished. All we want is to run the no-ip dynamic DNS update client in any machine in our local network. Note that we have ruled out running it on a PC for (in)convenience reasons and thats why we are exploring one of the embedded devices.

Patching the Huawei Quidway WA1003A

Ok, my modem IP address is 192.168.2.1

[root@cheetah ~]# telnet 192.168.2.1
Trying 192.168.2.1...
Connected to 192.168.2.1.
Escape character is '^]'.

BusyBox on (none) login:

Bingo! we get a nice login screen.  A quick google search reveals that the login credentials to use are user=root and password=admin. We provide that and a busybox shell welcomes us!

BusyBox on (none) login: root
Password: 

BusyBox v0.61.pre (2005.08.01-10:08+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

#

A quick set of Linux commands tells us that it is running a 2.4 Montavista Linux kernel on a MIPS based hardware, with 16MB of RAM.

# cat /proc/version
Linux version 2.4.17_mvl21-malta-mips_fp_le (root@Jackal) (gcc version 2.95.3 20010315 (release/MontaVista)) #114 Mon Aug 1 18:26:13 CST 2005
#
# cat /proc/cpuinfo
processor               : 0
cpu model               : MIPS 4KEc V4.8
BogoMIPS                : 149.91
wait instruction        : no
microsecond timers      : yes
extra interrupt vector  : yes
hardware watchpoint     : yes
VCED exceptions         : not available
VCEI exceptions         : not available
#
# cat /proc/meminfo
 total:    used:    free:  shared: buffers:  cached:
Mem:  14741504 14446592   294912        0  1425408  4030464
Swap:        0        0        0
MemTotal:        14396 kB
MemFree:           288 kB
MemShared:           0 kB
Buffers:          1392 kB
Cached:           3936 kB
SwapCached:          0 kB
Active:           5388 kB
Inactive:         1888 kB
HighTotal:           0 kB
HighFree:            0 kB
LowTotal:        14396 kB
LowFree:           288 kB
SwapTotal:           0 kB
SwapFree:            0 kB
#

These embedded boxes generally use flash as non-volatile store to store the kernel, filesystem and config data. Linux mtd driver is used to access these flash devices. Let see if/what we have for the flash.

# cat /proc/devices
Character devices:
 1 mem
 2 pty/m%d
 3 pty/s%d
 4 tts/%d
 5 cua/%d
 10 misc
108 ppp
128 ptm
136 pts/%d
162 raw

Block devices:
 31 mtdblock
#
# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 0035f000 00010000 "mtd0"
mtd1: 00080f70 00010000 "mtd1"
mtd2: 00010000 00002000 "mtd2"
mtd3: 00010000 00010000 "mtd3"
mtd4: 003e0000 00010000 "mtd4"
#
# ls -l /dev/mtdblock/*
brw-rw-rw-    1 0        0         31,   0 Jan  1  1970 /dev/mtdblock/0
brw-rw-rw-    1 0        0         31,   1 Jan  1  1970 /dev/mtdblock/1
brw-rw-rw-    1 0        0         31,   2 Jan  1  1970 /dev/mtdblock/2
brw-rw-rw-    1 0        0         31,   3 Jan  1  1970 /dev/mtdblock/3
brw-rw-rw-    1 0        0         31,   4 Jan  1  1970 /dev/mtdblock/4
# cat /proc/ticfg/env | grep mtd
mtd0    0x90091000,0x903f0000
mtd1    0x90010090,0x90091000
mtd2    0x90000000,0x90010000
mtd3    0x903f0000,0x90400000
mtd4    0x90010000,0x903f0000
#

What we get is that it an 4MB flash being accessed using the mtdblock driver, which allows the flash device to be accessed as a regular block device. Note that mtd4 is a virtual partition spanning both mtd0 and mdt1.

Backup, backup and again backup !

Before we try to do anything to the box, the rule of sanity tells us to backup as much of the data as we can. This will come very handy if we end up corrupting something and we want to restore things. Let us backup all the flash partitions as that is where all the non-volatile stuff is.  My plan is to dump each of these partitions and get that into my laptop. But how do we get stuff from the router to my laptop.  Luckily it has a tftp client.

# tftp
BusyBox v0.61.pre (2005.08.01-10:08+0000) multi-call binary

Usage: tftp [OPTION]... HOST [PORT]

#
# mount
/dev/mtdblock/0 on / type squashfs (ro)
none on /dev type devfs (rw)
proc on /proc type proc (rw)
ramfs on /var type ramfs (rw)
#

mount gives us some important info. We will need a writeable filesystem to dump the mtd partitions before we can tftp them out of the box. Looks like we already have /var that we can use. ramfs can usually extend as much as the available RAM allows, so if we are lucky and if we have enough RAM free, we can dump the biggest of the mtd partitions, which is 4MB.

Another important thing that mount tells us is that the root filesystem is present on /dev/mtdblock/0. This is our point of interest, as we want to add the noip client into the root filesystem and have the startup scripts updated so as to run the noip client when the modem boots up.

Ok, so here we go. I start the tftp server on my laptop and then on the router, I do

# cd /var/tmp/
# cat /dev/mtdblock/0 > mtd0
# tftp -p -l /var/tmp/mtd0 192.168.2.2
tftp: server says: File not found

Oops! Why is the server worried about this file. Of course, it is not present, as I’m putting it for the first time. Anyway, quickly I figured out that the Linux tftp server is expecting the destination file to be present. I would call this a bug but lets make progress. I touch that file on the server and then I get permission denied error. So, I chmod to 0777 and then the tftp starts, but… I see that tftp is stuck for  a very long time and the transfer has stalled. My networking background came handy and I tried to set the mtu of the router’s Ethernet interface to a lowish value and see if that helps.

And yes, it did..

# ifconfig  br0 mtu 500
# tftp -p -l /var/tmp/mtd0 192.168.2.2
#

Out of curiosity I figured out the lowest value that did not work. It turned out that 543 works and 544 does not. Anyway, getting back to our goal of backing up all the flash paritions, we execute the following series of commands.

# rm mtd0
# cat /dev/mtdblock/1 > mtd1
cat: read: Input/output error
# tftp -p -l /var/tmp/mtd1 192.168.2.2
# rm mtd1
# cat /dev/mtdblock/2 > mtd2
# tftp -p -l /var/tmp/mtd2 192.168.2.2
# rm mtd2
# cat /dev/mtdblock/3 > mtd3
# tftp -p -l /var/tmp/mtd3 192.168.2.2
# rm mtd3
# cat /dev/mtdblock/4 > mtd4
# tftp -p -l /var/tmp/mtd4 192.168.2.2
#

We get an IO error for mtd1 but looks like we get a major chunk of it, moreover we are not planning to update mtd1. As I said, my plan is to update the root filesystem which is on mtd0. Note how we delete the prev mtdX image as we dump the new one. This is because we do not want to run out of RAM at any point which may possibly cause our memory challenged router to crash.

Once in my laptop,  I quickly see what these files are

[root@cheetah ~]# file /var/tmp/mtd0
/var/tmp/mtd0: Squashfs filesystem, little endian, version 1.0, 2077245 bytes, 634 inodes, blocksize: 32768 bytes, created: Mon May 31 06:34:56 2010
[root@cheetah ~]# file /var/tmp/mtd1
/var/tmp/mtd1: data
[root@cheetah ~]# file /var/tmp/mtd2
/var/tmp/mtd2: data
[root@cheetah ~]# file /var/tmp/mtd3
/var/tmp/mtd3: data
[root@cheetah ~]# file /var/tmp/mtd4
/var/tmp/mtd4: data
[root@cheetah ~]#

Mounting the router’s root filesystem

As expected, mtd0 contains the squashfs filesystem.  This is good. Rest of the partitions will contain the kernel and the config, but maybe they are at some offset from the beginning of the partition and hence file(1) cannot see it. Lets move ahead.

We try to loop mount mtd0 as squashfs.

[root@cheetah ~]# mount -t squashfs -o loop /var/tmp/mtd0 /mnt/squash/
 mount: wrong fs type, bad option, bad superblock on /dev/loop0,
 missing codepage or helper program, or other error
 In some cases useful info is found in syslog - try
 dmesg | tail  or so

This fails with the following error in syslog

SQUASHFS error: Major/Minor mismatch, older Squashfs 1.0 filesystems are unsupported

Ok, so my laptop is running Linux 2.6.32 which has support for squashfs version 4. Searching through the mailing lists one can see that the squashfs was merged into Linux mainline in version 2.6.29 and it was decided that backward compatibility be dropped in favour of  cleaner code. So, there is no hope for me to be able to mount this older squashfs filesystem on my laptop running the latest 2.6. kernel ;-)

Since compiling an running a 2.4 kernel on a 2.6 based modern Linux distribution is quite challenging because of all the changes in gcc and various utilities, most notable being the modutils, I turn to an old RH8.0 VM that is a 2.4 based distribution.  Since squashfs was not merged in 2.4 kernel, we have to apply the patch. squashfs-1.3 appears to be our best bet as that is closest to version 1.0, so hopefully it will work. I downloaded Linux 2.4.24 kernel and applied the squashfs-1.3 patch.

Booted the patched 2.4.24 kernel and again attempted to loop mount the mtd0 file. Oops! again it fails, with the following error this time.

SQUASHFS error: zlib_fs returned unexpected result 0xfffffffd
SQUASHFS error: Unable to read cache block [1f6536:ace]
SQUASHFS error: Unable to read inode [1f6536:ace]
SQUASHFS error: Root inode create failed

Little bit of debugging and my friend Google tells me that this is because the squashfs filesystem used in my router uses LZMA compression and not the zlib compression which is the only one supported in the stock kernel.

Looking for squashfs with LZMA support, I reached this wonderful page that documents exactly what I was looking for.

http://www.beyondlogic.org/nb5/squashfs_lzma.htm

Followed the instructions given there to get

  1. 2.6.12.2 kernel patched with squashfs with LZMA support.
  2. squashfs-tools to create such an squashfs filesystem.

Lets boot the VM with the just compiled 2.6.12.2 kernel and try to loop mount the squashfs filesystem on mtd0.

root@vm-ubuntu801:~# mount -t squashfs -o loop /root/mtd0 /mnt/squash
root@vm-ubuntu801:~# cd /mnt/squash
root@vm-ubuntu801:/mnt/squash# ls
bin  dev  etc  lib  proc  sbin  usr  var  var.tar
root@vm-ubuntu801:/mnt/squash#

Bingo! it worked. So, now we can read the filesystem present on the router and can even modify it. Btw, we cannot add/modify files directly on the mounted squash filesystem, as it is a read-only filesystem. What we have to do is copy this filesystem to a directory, then update it there and then again pack it using the squashfs-tools we obtained using the instructions found in beyondlogic.org.

root@vm-ubuntu801:/mnt/squash# cd ..
root@vm-ubuntu801:/mnt# tar cf - squash | tar -C /tmp/ -xf -
root@vm-ubuntu801:/mnt#

Now we can update the filesystem and pack it back again. Like.

root@vm-ubuntu801: squashfs-tools# ./mksquashfs /tmp/squash squash-mod.img

I’ve put the patched 2.6.12.2 kernel sources –> here <–  and a statically linked squashfs binary –> here <–. You can use them or use the instructions provided in beyondlogic.org. If you are restless like me, you can use this tarball of the original squashfs filesystem. This will save you the effort of getting the kernel ready and mounting the squashfs image inside that. You can extract the tarball, update it as required and then use the static mksquashfs binary to create the updated squashfs filesystem image.

Ok, now we have all the tools to create an updated filesystem image for the WA1003A router. Lets step back and figure out what is that we want to change.

Creating a working noip client for my router

We want to add a properly configured no-ip client which we will start on bootup. www.no-ip.com provides the source code of their dynamic DNS update client.  Here is a list of things which we need to do

  1. Compile the no-ip client provided by www.no-ip.com for MIPS. For this you will need a MIPS toolchain. You can get one from here.
  2. Register with www.no-ip.com for a free account and create a hostname. tftp the no-ip client created above to /var/tmp in the router and create a config file in /var/tmp by running noip client with -C option.
  3. tftp the config file created above back to your host machine. We need to bundle this in the updated root filesystem for the router.

Note : The stock no-ip client has some problems running on the linux version that runs on the WA1003A. The shmctl() call keeps returning weird values which makes the noip client to think that there is already a copy running. This can be made to work with -M option to noip client, but sometimes even that does not help as it feels that more than max allowed instances are running.  We cannot afford these shaky starts on our router as we want it to work 100% of the times! I’ve updated the noip client to get rid of these errors and also added code to log a nice message in the syslog (which you can view through the routers web config interface) everytime it updates the IP address.

You can get the updated source –> here <–. You can also download the compiled noip program from –> here <–, this will save you the effort of downloading the MIPS toolchain and compiling the noip client ! If you are compiling your own noip2 client remember to use the “-s” option to gcc to strip off the symbol table as space is a premium in our router.

The above steps are detailed in this page. Read till the “Dynamic DNS” section.

At the end of the above exercise, we will have a MIPS executable for the no-ip client and a config file noip.conf which is prepared as per your noip account details.

Creating the updated rootfilesystem image

Remember, we have copied the rootfilesystem to /tmp/squash on our VM. This is what we will be updating.

root@vm-ubuntu801:/tmp/squash# ls
bin  dev  etc  lib  proc  sbin  usr  var  var.tar

We will do the following changes

  1. Add noip2 executable that we created above to usr/sbin/. Make sure to mark it executable.
  2. Update the etc/init.d/rcS startup script to invoke the above noip2 executable on startup.
  3. Update var.tar to include the var/noip/noip.conf configuration file. We have to add the config file to var.tar as noip client writes to it. As you would have noticed all the writable files go to var.tar which is extracted on a writeable ramfs at boot time.

Add the following line at the end of etc/init.d/rcS

#
# start the noip client with the hardcoded config
#
/usr/sbin/noip2 -M -c /var/noip/noip.conf

You can download the updated rcS from >> here <<. To update the var.tar, extract it in a temporary place and then tar it back after adding your var/noip/noip.conf file.

Once we have made all these changes to the root filesystem present on /tmp/squash, create the squashfs image like

root@vm-ubuntu801:/tmp/squash# cd ..
root@vm-ubuntu801:/tmp# ./mksquashfs squash/ /tmp/squashfs-mod.img
Creating little endian filesystem on /tmp/squashfs-mod.img, block size 32768.

Little endian filesystem, data block size 32768, compressed data, compressed metadata
Filesystem size 2028.58 Kbytes (1.98 Mbytes)
 30.13% of uncompressed filesystem size (6732.45 Kbytes)
Inode table size 4777 bytes (4.67 Kbytes)
 43.44% of uncompressed inode table size (10997 bytes)
Directory table size 4754 bytes (4.64 Kbytes)
 48.95% of uncompressed directory table size (9711 bytes)
Number of duplicate files found 51
Number of inodes 634
Number of files 513
Number of symbolic links  81
Number of device nodes 0
Number of fifo nodes 0
Number of socket nodes 0
Number of directories 40
Number of uids 1
 root (0)
Number of gids 0
root@vm-ubuntu801:/tmp#

You can try mounting the newly created squashfs image to see if it would really work on the router. It does get mounted, so the filesystem image is good!

Updating the Huawei Quidway WA1003A firmware

The beyondlogic page tells us that we have to add a checksum to the above image, else the router will not accept it as a valid firmware.  You can use >>this<< program to add the checksum. Just run the tichksum program with your filesystem image file as argument. It appends the correct checksum to the file in-place.

Now we have a valid signed rootfilesystem image for our ADSL router. How do we put it on the flash ?

!!DOUBT!! The image that we have created contains only the filesystem (and no kernel). Will the router support only filesystem upgrade and whether it will be able to identify the image as a filesystem image (and not a kernel or kernel+filesystem).

Lets try using the router’s Tools -> “Upate Gateway” page to update the filesystem we just created.

That seems to be stuck forever. Looking at the html code for that, I see that it is wrong and the firmware update can never work, let alone this special filesystem-only firmware that we have.

Without wasting much time, we move on to explore other options.

One thing that comes to my mind is writing the filesystem image directly on /dev/mtdblock/0.  Lets try that

# cat mtd0 >  /dev/mtdblock/0
cat: write: Operation not permitted
#

So looks like mtd0 is disallowed write access for understandable reasons.  lets see if we can do something to allow us to write. A quick look at the mtd code, and I write this small module to allow mtd0 to be written. If you do not have the toolchain or the Montavista kernel sources, I’ve a compiled module >>here<<.

# insmod ./mtdwriter.o
# cat mtd0 > /dev/mtdblock/0
# sync

Bingo! we could write our modified filesystem image.

With fingers crossed, I reboot the router waiting for the “Status” light to blink. It did start blinking, so the router was not bricked. But telnetting to the router still shows the old filesystem.

Looked more at the mtd code and I realize that mtd0 was readonly as it is not aligned on an erase block size. I believe for the same reason, it is not possible to write to it.

Note : After I played more with the router I realized that this would not have worked as I’ve not put the correct checksum with the image. It seems that the ADAM2 bootloader verifies the checksum at boot time and if not correct, it fails the boot.

What other options do we have ?

Looking at the mapping of various mtd partitions,

# cat /proc/ticfg/env | grep mtd
mtd0    0x90091000,0x903f0000
mtd1    0x90010090,0x90091000
mtd2    0x90000000,0x90010000
mtd3    0x903f0000,0x90400000
mtd4    0x90010000,0x903f0000

We can see that mtd4 is composed of mtd1 (which contains the kernel) and mtd0 (which contains the root filesystem image). It strikes to me if we could create a combo (kernel+filesystem) image and write that to mtd4.  Here is the new plan

  1. Get mtd4 image to my laptop. I know that 0×90010000 to 0×90091000 (i.e. 0×81000 or 528384 bytes) at the beginning  are the kernel and immediately after that we have the root filesystem image.
  2. Extract first 528384 bytes from mtd4 and keep it separate, this is our kernel.
  3. Append our newly formed rootfilesystem image to it, to get a combo image to flash on mtd4.
  4. Add a correct checksum to this image using the tichksum utility, as discussed above.
  5. Write this properly signed image to mtd4 and pray (Note: In the previous step, the kernel module that I wrote revealed to me that mtd4 is writeable).
[root@cheetah prepare_fw_img]# xxd mtd4 | grep 0081000:
0081000: 6873 7173 7902 0000 cf7b 1f00 cb7b 1f00  hsqsy....{...{..
[root@cheetah prepare_fw_img]# dd if=mtd4 of=kernel.bin bs=528384 count=1
[root@cheetah prepare_fw_img]# cat kernel.bin squashfs-mod.img > fw.bin
[root@cheetah prepare_fw_img]# ../TI-chksum-0.1/tichksum fw.bin

Ok, let me explain a few things before we flash our shiny new image.

The “hsqs” (this is the squashfs magic sqsh in little endian format) in the xxd o/p confirms that we indeed have the squash filesystem @ offset 0×81000. Offline, I figured out that the checksum has to be put for the combined image and not for the filesystem alone. I did this by looking at the original mtd4 image checksum.

Now lets tftp this fw.bin to the box and try flashing.

# tftp -g -r /var/tmp/fw.bin 192.168.2.2
# cat /var/tmp/fw.bin > /dev/mtdblock/4
#

As expected, we could write to mtd4 w/o any problems. Lets pray and reboot the box!

Unfortunately the “Status” light does not come up, and after couple minutes wait, I’m pretty certain that the worst has happened. I’ve bricked my modem. Since, this is the only modem I’ve and I am at home right now, I’ve lost all connections to the world, I’ll have to go to a nearby internet cafe and figure out my next steps.

This entry was posted in Uncategorized. Bookmark the permalink.

2 Responses to How I saved 20$/year in two days, or, how to hack Huawei WA1003A modem+router to do free dynamic DNS updates

  1. vivek khurana says:

    lol! i was quite impressed and was about to follow your procedure. until i read the last para. so you were not able to get it to work?

    i have a very similar problem and running the no-ip client within the quidway wa1003a would be awesome for me too…

    did you just brick ur router? did the backup that you took help in anyway?

    vivek

  2. admin says:

    Sorry for the delay in the last part of this article.
    Actually I was able to make it work and its working great for me, but unfortunately I lost my notes documenting the exact steps. I’ll try to recall the steps and document them soon, but since I cannot verify them, they will be “use at your own risk” :-)

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>