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.

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:

Identifying the ELF program section where the address is located 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:

Locate the offset of the first file path in the httpd binary (typically Alive.asp) Convert the physical offset of this string to a virtual address 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:

You cannot add files 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.