Modifying The DD-WRT GUI – /dev/ttyS0

Although released under the GPL, DD-WRT is notoriously difficult to build from source. If you want to customize your DD-WRT installation, it is usually easier to extract files from the firmware image, change what you need, and then re-construct the image.

One exception here is the Web GUI. The DD-WRT Web pages (*.asp, *.htm, *.gif, *.css) in each firmware image are protected in order to prevent modification. Being able to customize the Web interface can be advantageous for those wishing to add compatibility with mobile/uncommon browsers, change themes, add links, etc.

And, despite claims to the contrary, that’s exactly what we’ll be doing.

DD-WRT Sporting the Hack-A-Day Logo

Starting on this project, my first assumption was that the Web pages were built into the httpd binary itself. However, taking a look at the source code, we can see that the function getWebsFile actually reads data from the file /etc/www:

263     FILE *getWebsFile(char *path)
264	{
265	        cprintf("opening %s", path);
266	        int i = 0;
267	        while (websRomPageIndex[i].path != NULL) {
268	                if (!strcmp(websRomPageIndex[i].path, path)) {
269	                        FILE *web = fopen("/tmp/www.debug", "rb");
270	                        if (!web)
271	                            web = fopen("/etc/www", "rb");
272	                        if (web == NULL)
273	                                return NULL;
274	                        fseek(web, websRomPageIndex[i].offset, 0);
275	                        cprintf("found %s", path);
276	                        return web;
277	                }
278	                i++;
279	        }
280	        cprintf("not found %s", path);
281	
282	        return NULL;
283	}

At build time, all of the Web files are concatenated into the /etc/www file; websRomPageIndex is used by the httpd code to identify where each file is located inside of /etc/www.

websRomPageIndex points to an array of data structures that each contain three elements: a pointer to a Web URL string, the size of the file, and the file’s location inside /etc/www:

82	typedef struct {
83	        char *path;             /* Web page URL path */
84	        unsigned int offset;    /* Web page data */
85	        unsigned int size;      /* Size of web page in bytes */
86	} websRomPageIndexType;

UPDATE:

Not long after this article was published, websRomPageIndexType was modified so that newer DD-WRT builds use the following structure:

82	typedef struct {
83	        char *path;             /* Web page URL path */
84	        unsigned int size;      /* Size of web page in bytes */
85	} websRomPageIndexType;

The offset is not explicitly specified and must be inferred based on the sizes of the previous files. Both the old and new formats are supported by webdecomp.

If we can locate websRomPageIndex in the httpd binary, we will be able to identify and extract each Web page from the /etc/www file and associate the extracted data with the appropriate file name.

There are several ways to locate websRomPageIndex. First, IDA resolves the name properly, making it easy to identify the virtual address of websRomPageIndex:


For MIPS binaries, readelf can also be used to identify the websRomPageIndex virtual address:

$ readelf --arch-specific httpd | grep websRomPageIndex
  004372e8 -31416(gp) <unknown> 004352c8 OBJECT   19 websRomPageIndex

These virtual addresses can be converted into file offsets by:

  1. Identifying the ELF program section where the address is located
  2. Subtracting the section virtual address and adding the section file offset to the address

Here, we see that the address 0x004352C8 is located in the second PT_LOAD section:

$ readelf --program-headers httpd

Elf file type is EXEC (Executable file)
Entry point 0x403950
There are 7 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00400034 0x00400034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x00400114 0x00400114 0x00014 0x00014 R   0x1
      [Requesting program interpreter: /lib/ld-uClibc.so.0]
  LOAD           0x000000 0x00400000 0x00400000 0x24e44 0x24e44 R E 0x10000
  LOAD           0x025000 0x00435000 0x00435000 0x023ab 0x084bc RW  0x10000
  DYNAMIC        0x000128 0x00400128 0x00400128 0x00128 0x00128 RWE 0x4
  GNU_EH_FRAME   0x0023ab 0x00424e30 0x00424e30 0x00000 0x00014 R   0x4
  NULL           0x000000 0x00000000 0x00000000 0x00000 0x00000     0x4

We can now calculate the file offset of websRomIndexPage:

$ echo "$((0x004352C8)) - $((0x00435000)) + $((0x025000))" | bc
152264

Since each structure in the websRomIndexPage array contains a pointer to the Web file path, another method of locating websRomIndexPage is to:

  1. Locate the offset of the first file path in the httpd binary (typically Alive.asp)
  2. Convert the physical offset of this string to a virtual address
  3. Search the binary for references to the virtual address

Once the websRomIndexPage location has been identified, we can simply walk through the structures and extract the corresponding file data from /etc/www until we find a structure with a NULL path, indicating the end of the structure array.

Looking at the first structure entry at offset 152264 (0x252C8), we see that the path pointer points to 0x0041F1F4, the offset into the /etc/www file is 0x00000000, and the file size is 0x00000C6C:

000252c0  04 3f 42 00 30 50 43 00  f4 f1 41 00 00 00 00 00  |.?B.0PC...A.....|
000252d0  6c 0c 00 00 00 f2 41 00  6c 0c 00 00 18 60 00 00  |l.....A.l....`..|

Converting the virtual pointer address 0x0041F1F4 to a physical file offset we get 0x1F1F4, which is where the string “Alive.asp” is located:

0001f1f0  25 3e 00 00 41 6c 69 76  65 2e 61 73 70 00 00 00  |%>..Alive.asp...|

We can now extract the contents of Alive.asp from /etc/www:

$ dd if=www bs=$((0xC6C)) count=1 of=Alive.asp
1+0 records in
1+0 records out
3180 bytes (3.2 kB) copied, 3.106e-05 s, 102 MB/s

And verify that the data looks correct:

<% do_pagehead("alive.titl"); %>
{m}
//<![CDATA[
function to_submit(F) {
F.save_button.value = sbutton.saving;
apply(F);
}
function to_apply(F) {
F.save_button.value = sbutton.saving;
applytake(F);
}
...
</div>
{e}"info"><% tran("share.time"); %>:  <span id="uptime"><% get_uptime(); %></span></div>
{e}"info">WAN<span id="ipinfo"><% show_wanipinfo(); %></span></div>
</div>
</div>
</div>
</body>
</html>

Looks good! We can now modify this file however we want, then copy it back into /etc/www. However, if we change the size of Alive.asp we will also need to update its file size, as well as the file offsets of all subsequent files, in the websRomPageIndex structure array.

Although this process is not terribly difficult, it is time consuming; obviously, some automation is in order. To that end, I’ve written webdecomp, a tool to automate the extraction and restoration of DD-WRT Web files. It has been added to the firmware-mod-kit, and allows you to change the Web pages however you wish, with the following restrictions:

  1. You cannot add files
  2. You cannot delete files (but you can have empty files)

So far it has been tested on various DD-WRT builds for ARMEB, MIPS and MIPSEL, but should work with most architectures and build versions without issue.

Bookmark the permalink.

Comments are closed.