Exploit research

1. Initial findings

1.1 file

$ file httpd
httpd: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped

1.2 checksec

$ pwn checksec httpd
    Arch:     mips-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
  • We can see that the httpd binary does not have any form of protection

  • We are not directly able to identify the presence of ASLR

    • Given that PIE is disabled, it is likely that ASLR will be enabled (or at least partially)

    • We can use the following command to find out:

The file contains an integer value with different meanings:

  • 0 -> no ASLR

  • 1 -> partial ASLR

  • 2 -> full ASLR

  • In our case, the file contains the value 1

In the later sections (CVE-2025-60690arrow-up-right), we will able to see that the value of the stack pointer (SP) and stack addresses changes for each execution

1.3 Runtime analysis

For now, let's perform some runtime analysis to identify if ASLR is enabled on the libcarrow-up-right (/lib/libc.so.0) library

  • The commands listed below (to identify the address of the libc library) should be ran across the following conditions:

  1. Device reboots

  2. Re-executions of the vulnerable binary (httpd)

In all cases, the address is shown to be constant at 0x2ad38000:

  • The dynamic loader (/lib/ld-uClibc.so.0) appears to be constant too:

1.3 objdump

Note that the objdump binary used is from the binutils-mipsel-linux-gnu library

2. GDB, gdbserver

2.1 Retrieve gdbserver for the target's architecture

To start off, we have to retrieve the gdbserver binary for our target architecture mipsel (mips little-endian):

Remember to always check the available memory space available on the device, before transferring files

2.2 gdbserver on target router

Next, we can run gdbserver on the target router:

We can use the steps outlined in the previous sectionsarrow-up-right to transfer the gdbserver binary from host to the device

Alternatively, launch from PID of a running program:

2.3 Connect to remote target from host machine

  • To debug (using gdb-multiarch) from the host machine

NOTE: the normal gdb will not work since it does not support the mips architecture

  • Since we know that our router uses the mipsel architecture (simply mips with little-endian), we have to set it up appropriately

2.4 Memory address enumeration (httpd)

3. Enumeration

3.1 Ghidra

  1. Window -> Function call graph

  2. Window -> Function call tree

  3. Window -> Defined strings

    1. Right-click -> Data -> ... eg. TerminatedCString

    2. Right-click -> References -> show TerminatedCstring

  4. Right-click -> References -> show references to ...

  5. ...

3.2 Fuzzing

From UART console

  • Listen with gdbserver

  • It appears that sending a VALID POST request to /apply.cgi triggers a certain system reset (as seen from the UART console), with the following logs:

  • Only a VALID POST request parameter will trigger this behavior, while an invalid one (such as --data "action=Apple") will not:

Interesting observation

  • For the POST request with an invalid action parameter passed to the --data "action=xxxx" option, we get a response string:

  • Searching for the value "invalid" in the apply_cgi function (Ghidra), we find the following code snippet:

  • A more comprehensive code snippet:

  • Other valid values appears to be "Restore" and "Reboot"

3.3 index.asp

file-download
59KB

Note that this web page automatically loads the index.asp file. This information will be useful for further analysis in the later steps

The index.asp file can be found in the /www directory (/www/index.asp)

From the initial research (in the Initial research page), we can understand that most of the CVEs found are relating to the httpd binary. Looking further into the description of each, we can see that quite a few of them involves CGI functions (CVE-2025-60689arrow-up-right, CVE-2025-60690arrow-up-right, CVE-2025-60691arrow-up-right, CVE-2025-60693arrow-up-right, CVE-2025-60694arrow-up-right)

Let's start off by searching for the term ".cgi" in the index.asp file. Right off the bat, we find the following code snippet:

3.4.1 Extract information

  • We can extract the following information:

  1. Identifier for this form (name=setup)

  • likely referenced by:

    • document.setup

    • document.forms["setup"]

  1. CGI action (action=apply.cgi)

  • The /apply.cgi path will be called

3.4.2 Identifying form identifier locations

We can attempt to identify the functions that handles the form action by searching for the following values:

  • document.setup

    • multiple results

  • document.forms["setup"] (or document.forms['setup'])

    • none

3.4.3 Interesting code locations of document.setup

  1. Passed as arguments to functions

The following shows a code snippet where document.setup was passed as argument to the reboot() function:

  • From the reboot() function, we can observe that it accepts the argument value of document.setup as the variable F

  • We can also observe similar patterns for other functions:

    • ppp_enable_disable

    • dhcp_enable_disable

    • dslite_enable_disable

    • mtu_enable_disable

  1. Assigned to local variable in a function

  • This can be found in the selpptppmode() function too

3.4.4 Identifying form action handler(s)

In JavaScript, the submit method can be called on the document.name instance to trigger a form action

Thus, we can perform a search for the value .submit (or more specifically .submit()), in order to identify the functions that handles the form action

NOTE: we can also search directly for the term: F.submit()

The code snippet can be found in the following functions:

  1. selWan

  2. selPPP

  3. reboot

  4. to_submit

  5. selLang

From the results, we should observe that most of the functions will have the following code:

  • This supports our observation from the previous analysis, where the variable name F is commonly used to reference the document.setup instance

The following functions are invoked by a certain HTML form present on the web page:

  1. selWan

  2. reboot

  3. selLang

Refer to the subsequent CVE sections to understand how we can trigger the found functions

3.4 Web page (port 80) traffic analysis

Upon navigating to the web page on port 80, we are prompted for a username and password. A simple search in a search engine for "linksys e1200 default credentials" yields the following results:

https://netstorage.ringcentral.com/datasheets/routers/linksys/e1200_v06.1.pdfarrow-up-right

The username and password combination is admin, admin

It is likely that the index.asp file is loaded whenever a request is sent to the root URL of the webpage on port 80. We know from previous analysis that there exist a form element that calls the apply.cgi route, which triggers the apply_cgi function

3.4.1 Manually send form request from the webpage

We can manually send a form request, and use the web browser to analyze the HTTP traffic to understand the parameters and values involved

Refer to the notes under the Exploit research page to understand more about each function discussed below

Payload analysis

  • The following outlines the payload for each of the specific forms:

  1. SelWAN() function

At the top of the web page, we can see the following outlined form field:

  • From anaylsis of the HTML source code, we can identify the following FUNNAME1 class:

  • Next, we can experiment with a few values from the selection options, and record the Payload parameters (form-data) that are automatically generated by the code, and sent with the request

NOTE: we require proper authentication with the Authorization: Basic xxxx request headers, and the response should have a 200 OK status code

The basic token value YWRtaW46YWRtaW4= is the base64-encoded string of the value admin:admin

a. Automatic Configuration - Static IP

b. Automatic Configuration - DHCP

  • Note a few important parameter names such as action , lan_ipaddr_0 , lan_ipaddr_1 , etc.

4. Setting custom breakpoints on a binary (workaround)

4.1 Binary patching

In the subsequent sections, we will discover that we are unable to use the built-in GDB commands to set a custom breakpoint, as there appears to be some form of protection from the device kernel or other feature:

  • Using hardware breakpoint fails too:

Fortunately, we can use a creative workaround to set a custom breakpoint, by manually patching the binary:

Points to note:

  1. \x0d\x00\x00\x00 represents the value 0x0000000d in little-endian, which is a break instruction

  2. We will overwrite the address at 0xXXXXXXXX

  • Indicated by the seek option

  1. It is important to include the conv=notrunc option to prevent truncation of the data following the "seek" address

Refer to the next few specific CVE research sections to understand how we can apply this method (eg. CVE-2025-60690arrow-up-right)

4.2 Manual SIGSTOP signal to halt the running process (gdb)

Right after the breakpoint is hit, we need to manually send a SIGSTOP signal to halt the running instance on gdb, and allow us to view the relevant values: stack, registers, threads, etc.

Note that this command must be ran from a separatearrow-up-right console from the one running the gdbserver instance

Last updated