Seven years ago, before the $35 Raspberry Pi, hackers used commercial WiFi routers for their projects. They’d replace the stock firmware with Linux. The $22 TP-Link WR703N was extremely popular for these projects, being half the price and half the size of the Raspberry Pi.
Unfortunately, these devices had extraordinarily limited memory (16-megabytes) and even more limited storage (4-megabyte). That’s megabytes – the typical size of an SD card in an RPi is a thousand times larger.
I’m interested in that device for the simple reason that it has a big-endian CPU.
All these IoT-style devices these days run ARM and MIPS processors, with a smattering of others like x86, PowerPC, ARC, and AVR32. ARM and MIPS CPUs can run in either mode, big-endian or little-endian. Linux can be compiled for either mode. Little-endian is by far the most popular mode, because of Intel’s popularity. Code developed on little-endian computers sometimes has subtle bugs when recompiled for big-endian, so it’s best just to maintain the same byte-order as Intel. On the other hand, popular file-formats and crypto-algorithms use big-endian, so there’s some efficiency to be gained with going with that choice.
I’d like to have a big-endian computer around to test my code with. In theory, it should all work fine, but as I said, subtle bugs sometimes appear.
The problem is that the base Linux kernel has slowly grown so big I can no longer get things to fit on the WR703N, not even to the point where I can add extra storage via the USB drive. I’ve tried to hack a firmware but succeeded only in bricking the device.
So this post is about the steps I took to get things working for myself.
The first step is to connect to the device. One way to do this is connect the notebook computer to their WiFi, then access their web-based console. Another way is to connect to their “LAN” port via Ethernet. I chose the Ethernet route.
The problem with their Ethernet port is that you have to manually set your IP address. Their address is 192.168.8.1. I handled this by going into the Linux virtual-machine on my computer, putting the virtual network adapter into “bridge” mode (as I always do anyway), and setting an alternate IP address:
# ifconfig eth:1 192.168.8.2 255.255.255.0
The firmware I want to install is from the OpenWRT project which maintains Linux firmware replacements for over a hundred different devices. The device actually already uses their own variation of OpenWRT, but still, rather than futz with theirs I want to go with a vanilla installation.
I download this using the browser in my Linux VM, then browse to 192.168.8.1, navigate to their firmware update page, and upload this file. It’s not complex – they actually intend their customers to do this sort of thing. Don’t worry about voiding the warranty: for a ~$20 device, there is no warranty.
The device boots back up, this time the default address is going to be 192.168.1.1, so again I add another virtual interface to my Linux VM with “ifconfig eth:2 192.168.1.2” in order to communicate with it.
I now need to change this 192.168.1.x setting to match my home network. There are many ways to do this. I could just reconfigure the LAN port to a hard-coded address on my network. Or, I could connect the WAN port, which is already configured to get a DHCP address. Or, I could reconfigure the WiFi component as a “client” instead of “access-point”, and it’ll similarly get a DHCP address. I decide upon WiFi, mostly because my 24 port switch is already full.
The problem is OpenWRT’s default WiFi settings. It’s somehow interfering with accessing the device. I can’t see how reading the rules, but I’m obviously missing something, so I just go in and nuke the rules. I just click on the “WAN” segment in the firewall management page and click “remove”. I don’t care about security, I’m not putting this on the open Internet or letting guests access it.
To connect to WiFi, I remove the current settings as an “access-point”, then “scan” my local network, select my access-point, enter the WPA2 passphrase, and connect. It seems to work perfectly.
While I’m here, I also go into the system settings and change the name of the device to “MipsDev”, and also set the timezone to New York.
I then disconnect the Ethernet and continue at this point via their WiFi connection. At some point, I’m going to just connect power and stick it in the back of a closet somewhere.
DHCP assigns this 10.20.30.46 (I don’t mind telling you – I’m going to renumber my home network soon). So from my desktop computer I do:
C:> ssh [email protected]
…because I’m a Windows user and Windows supports ssh now gdd**it.
OpenWRT had a brief schism a few years ago with the breakaway “LEDE” project. They mended differences and came back together again the latest version. But this older version still goes by the “LEDE” name.
At this point, I need to expand the storage from the 16-megabytes on the device. I put in a 32-gigabyte USB flash drive for $5 – expanding storage by 2000 times.
The way OpenWRT deals with this is called an “overlay”, which uses the same technology has Docker containers to essentially containerize the entire operating system. The existing operating system is mounted read-only. As you make changes, such as installing packages or re-configuring it, anything written to the system, is written into the overlay portion. If you do a factory reset (by holding down the button on boot), it simply discards the overlay portion.
What we are going to do is simply change the overlay from the current 16-meg on-board flash to our USB flash drive. This means copying the existing overlay part to our drive, then re-configuring the system to point to our USB drive instead of their overlay.
This process is described on OpenWRT’s web page here:
It works well – but for systems with more than 4-megs. This is what defeated me before, there’s not enough space to add the necessary packages. But with 16-megs on this device there is plenty off space.
The first step is to update the package manager, such like on other Linuxs.
# opkg update
When I plug in the USB drive, dmesg tells me it finds a USB “device”, but nothing more. This tells me I have all the proper USB drivers installed, but not the flashdrive parts.
[ 5.388748] usb 1-1: new high-speed USB device number 2 using ehci-platform
Following the instructions in the above link, I then install those components:
# opkg install block-mount kmod-fs-ext4 kmod-usb-storage-extras
Simply installing these packages will cause it to recognize the USB drive in dmesg:
[ 10.748961] scsi 0:0:0:0: Direct-Access Samsung Flash Drive FIT 1100 PQ:
0 ANSI: 6
[ 10.759375] sd 0:0:0:0: [sda] 62668800 512-byte logical blocks: (32.1 GB/29.9 G
[ 10.766689] sd 0:0:0:0: [sda] Write Protect is off
[ 10.770284] sd 0:0:0:0: [sda] Mode Sense: 43 00 00 00
[ 10.771175] sd 0:0:0:0: [sda] Write cache: enabled, read cache: enabled, doesn’
t support DPO or FUA
[ 10.788139] sda: sda1
[ 10.794189] sd 0:0:0:0: [sda] Attached SCSI removable disk
At this point, I need to format the drive with ext4. The correct way of doing this is to connect to my Linux VM and format it that way. That’s because in these storage limited environments, OpenWRT doesn’t have space for such utilities to do this. But with 16-megs that I’m going to overlay soon anyway, I don’t care, so I install those utilities.
# opkg install e2fsprogs
Then I do the normal Linux thing to format the drive:
# mkfs.ext4 /dev/sda1
This blows away whatever was already on the drive.
Now we need to copy over the contents of the existing /overlay. We do that with the following:
# mount /dev/sda1 /mnt
# tar -C /overlay -cvf - . | tar -C /mnt -xf -
# umount /mnt
We use tar to copy because, as a backup program, it maintains file permissions and timestamps. So it’s better to backup and restore. We don’t want to actually create a file, but instead use it in streaming mode. The ‘-’ on one invocation causes it to stream the results to stdout instead of writing a file. the other invocation uses ‘-’ to stream from stdin. Thus, we never create a complete copy of the archive, either in memory or on the disk. We untar files as soon as we tar them up.
At this point we do a blind innovation I really don’t understand. I just did it and it works. The link above has some more text on this, and some things you should check afterwards.
# block detect > /etc/config/fstab; <br /> sed -i s/option$’\t’enabled$’\t’‘0’/option$’\t’enabled$’\t’‘1’/ /etc/config/fstab; <br /> sed -i s#/mnt/sda1#/overlay# /etc/config/fstab; <br /> cat /etc/config/fstab;
At this point, I reboot and relogin. We need to update the package manager again. That’s because when we did it the first time, it didn’t include packages that could fit in our tiny partition. We update again now with a huge overlay to get a list of all the package.
# opkg update
For example, gcc is something like 50 megabytes, so I wouldn’t have fit initially, and now it does. it’s the first thing I grab, along with git.
# opkg install gcc make git
Now I add a user. I have to do this manually, because there’s no “adduser” utility I can find that does this for me. This involves:
- adding line to /etc/passwd
- adding line to /etc/group
- using passwd command to change the password for the accountt
- creating a directory for the user
- chown the user’s directory
My default shell is /bin/ash (BusyBox) instead of /bin/bash. I haven’t added bash yet, but I figure for a testing system, maybe I shouldn’t.
I’m missing a git helper for https, so I use the git protocol instead:
$ git clone git://github.com/robertdavidgraham/masscan
$ cd masscan
At this point, I’d normally run “make -j” to quickly build the project, starting a separate process for each file. This compiles a lot faster, but this device only has 64-mgs of RAM, so it’ll run out of space quickly. Each process needs around 20-megabytes of RAM. So I content myself with two threads:
$ make -j 2
That’s enough such that one process can be stuck waiting to read files while the other process is CPU bound compiling. This is a slow CPU, so that’s a big limitation.
The final linking step fails. That’s because this platform uses different libraries than other Linux versions, the musl library instead of glibc you find on the big distros, or uClibc on smaller distros, like those you’d find on the Raspberry Pi. This is excellent – I found my first bug I need to fix.
In any case, I need to verify this is indeed “big-endian” mode, so I wrote a little program to test it:
int x = (int)"\1\2\3\4";
It indeed prints the big-endian result:
The numbers would be reversed if this were little-endian like x86.
Anyway, I thought I’d document the steps for those who want to play with these devices. The same steps would apply to other OpenWRT devices. GL-iNet has some other great options to work with, but of course, after some point, it’s just easier getting Raspberry Pi knockoffs instead.
Article Link: https://blog.erratasec.com/2018/09/mini-pwning-with-gl-inet-ar150.html