What's new
  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

Reversing the web node reboot feature

cyruz

Occasional Visitor
Hi guys,

for scripting reasons (Apple Shortcuts to be honest), I needed to restart a mesh node from the main, but I did not want to rely on cascading SSH connections.

I tried to understand how the reboot from the web page works and these are my findings:

The reboot event triggered by the click brought me to the httpApi.nvramSet call:

Code:
httpApi.nvramSet({
"device_list" : (specific_node_data.mac).toUpperCase(),
"action_mode": "device_reboot"
});

Searching for the action mode device_reboot in the Merlin repo, I didn't get any significant result. Strange, because after reading some code related to the http daemon, I found that there is a reference to this in the asuswrt-merlin.ng/release/src/router/httpd/web.c :

Code:
if (!strcmp(action_mode, "device_reboot")) {
    event_id = EID_HTTPD_REBOOT;
} else if (!strcmp(action_mode, "re_reconnect")) {
    event_id = EID_HTTPD_RE_RECONNECT;
} else if (!strcmp(action_mode, "force_roaming")) {
    if (client_mac) {
        event_id = EID_HTTPD_FORCE_ROAMING;
        strlcat(data, "{", sizeof(data));
        memset(value, 0, sizeof(value));
        snprintf(value, sizeof(value), "\"%s\":\"%s\"", STA, client_mac);
        strlcat(data, value, sizeof(data));
        if (block_time) {
            memset(value, 0, sizeof(value));
            snprintf(value, sizeof(value), ",\"%s\":\"%s\"", BLOCK_TIME, block_time);
            strlcat(data, value, sizeof(data));
        }

        if (target_ap) {
            memset(value, 0, sizeof(value));
            snprintf(value, sizeof(value), ",\"%s\":\"%s\"", TARGET_AP, target_ap);
            strlcat(data, value, sizeof(data));
        }
        strlcat(data, "}", sizeof(data));
    }
    else
        dbg("no client mac\n");
}
else
    dbg("can't find event id\n");

if (event_id > 0) {
    if (strlen(data) == 0)
        strlcpy(data, "{}", sizeof(data));

    snprintf(event_msg, sizeof(event_msg), HTTPD_ACTION_MSG,
        event_id, mac_list, data);
    send_cfgmnt_event(event_msg);
}

So the string HTTPD_ACTION_MSG is formatted with an event id, a mac address list and some arbitrary empty data.
Found the definitions in asuswrt-merlin.ng/release/src/router/cfg_mnt/cfg_event.h:

Code:
#ifndef __CFG_EVENT_H__
#define __CFG_EVENT_H__

#define WEVENT_PREFIX	"wevent"
#define HTTPD_PREFIX	"httpd"
#define RC_PREFIX		"rc"
#define ETHEVENT_PREFIX	"ethevent"
#define EVENT_ID	"eid"
#define MAC_ADDR	"mac_addr"
#define IF_NAME		"if_name"
#define SLAVE_MAC	"slave_mac"
#define LOGIN_IP		"login_ip"
#define OB_STATUS	"ob_status"
#define OB_KEY	"ob_key"
#define RE_MAC		"re_mac"
#define NEW_RE_MAC	"new_re_mac"
#define VSIE		"vsie"
#define ASUS_OUI	"F832E4"
#define CONFIG	"config"
#define E_MODEL_NAME	"model_name"
#define E_ETHER_LIST	"ether_list"
#define E_OB_PATH	"ob_path"
#define MAC_LIST	"mac_list"
#define DATA		"data"
#define STA		"sta"
#define BLOCK_TIME	"block_time"
#define TARGET_AP	"target_ap"
#define ROUTERBOOST_PREFIX "routerboost"
#define PRI_MAC_ADDR "pri_mac"
#define SLV_MAC_ADDR "slv_mac"
#ifdef RTCONFIG_AMAS_CENTRAL_OPTMZ
#define BAND_INDEX	"band_index"
#endif
#ifdef RTCONFIG_AMAS_CENTRAL_ADS
#define SEQUENCE	"seq"
#endif
#define WEVENT_GENERIC_MSG	 "{\""WEVENT_PREFIX"\":{\""EVENT_ID"\":\"%d\"}}"
#define WEVENT_MAC_IFNAME_MSG	 "{\""WEVENT_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""MAC_ADDR"\":\"%s\",\""IF_NAME"\":\"%s\"}}"
#define WEVENT_VSIE_MSG	 "{\""WEVENT_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""VSIE"\":\"%s\"}}"
#define HTTPD_GENERIC_MSG	 "{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\"}}"
#define HTTPD_SLAVE_MSG	"{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""SLAVE_MAC"\":\"%s\"}}"
#define HTTPD_IP_MSG	"{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""LOGIN_IP"\":\"%s\"}}"
#define HTTPD_OB_AVAILABLE_MSG	"{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""OB_STATUS"\":%d}}"
#define HTTPD_OB_LOCK_MSG	"{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""OB_STATUS"\":%d,\""RE_MAC"\":\"%s\",\""NEW_RE_MAC"\":\"%s\"}}"
#define HTTPD_OB_SELECTION_MSG	"{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""OB_STATUS"\":%d,\""NEW_RE_MAC"\":\"%s\",\""E_OB_PATH"\":%d}}"
#define HTTPD_CONFIG_CHANGED_MSG	"{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""RE_MAC"\":\"%s\",\""CONFIG"\":%s}}"
#define HTTPD_REBOOT_MSG	"{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""MAC_LIST"\":%s}}"
#define HTTPD_ACTION_MSG	"{\""HTTPD_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""MAC_LIST"\":%s,\""DATA"\":%s}}"
#define RC_GENERIC_MSG	 	"{\""RC_PREFIX"\":{\""EVENT_ID"\":\"%d\"}}"
#define RC_CONFIG_CHANGED_MSG	"{\""RC_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""CONFIG"\":%s}}"
#ifdef RTCONFIG_AMAS_CENTRAL_OPTMZ
#define RC_OPT_SS_RESULT_MSG	"{\""RC_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""BAND_INDEX"\":%d}}"
#endif
#define ETHEVENT_PROBE_MSG	 "{\""ETHEVENT_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""E_ETHER_LIST"\":%s}}"
#define ETHEVENT_STATUS_MSG	 "{\""ETHEVENT_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""OB_STATUS"\":%d,\""OB_KEY"\":\"%s\"}}"
#define ROUTERBOOST_STATUS_MSG	 "{\""ROUTERBOOST_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""PRI_MAC_ADDR"\":\"%s\",\""SLV_MAC_ADDR"\":\"%s\"}}"
#ifdef RTCONFIG_AMAS_CENTRAL_ADS
#define RC_ADS_DS_MSG	"{\""RC_PREFIX"\":{\""EVENT_ID"\":\"%d\",\""SEQUENCE"\":%d}}"
#endif

/* source */
#define FROM_NONE	0x0
#define FROM_WIRELESS	0x1
#define FROM_ETHERNET	0x2

enum httpdEventType {
	EID_HTTPD_NONE = 0,
	EID_HTTPD_FW_CHECK = 1,
	EID_HTTPD_FW_UPGRADE,
	EID_HTTPD_REMOVE_SLAVE,
	EID_HTTPD_RESET_DEFAULT,
	EID_HTTPD_ONBOARDING,
	EID_HTTPD_CONFIG_CHANGED,
	EID_HTTPD_START_WPS,
#ifdef RTCONFIG_BHCOST_OPT
	EID_HTTPD_SELF_OPTIMIZE,
#endif
	EID_HTTPD_REBOOT,
	EID_HTTPD_RE_RECONNECT,
	EID_HTTPD_FORCE_ROAMING,
	EID_HTTPD_MAX
};

enum wEventType {
	EID_WEVENT_DEVICE_CONNECTED = 1,
	EID_WEVENT_DEVICE_DISCONNECTED,
	EID_WEVENT_DEVICE_PROBE_REQ,
	EID_WEVENT_DEVICE_RADAR_DETECTED
};

enum ethEventType {
	EID_ETHEVENT_DEVICE_PROBE_REQ = 1,
	EID_ETHEVENT_ONBOARDING_STATUS
};

enum rcEventType {
	EID_RC_WPS_STOP = 1,
	EID_RC_REPORT_PATH,
	EID_RC_GET_TOPOLOGY,
	EID_RC_FEEDBACK,
	EID_RC_RESTART_WIRELESS,
	EID_RC_CONFIG_CHANGED,
	EID_RC_OPT_SS_RESULT,
	EID_RC_OPT_NOTIFY,
#ifdef RTCONFIG_AMAS_CENTRAL_ADS
	EID_RC_REPORT_DS_RESULT,
	EID_RC_REPORT_DS_SWITCH_STA_DISCONN,
#endif
	EID_RC_MAX
};

enum rbEventType {
	EID_RB_STA_CONN = 1,
	EID_RB_STA_DISCONN
};

#endif /* __CFG_EVENT_H__ */
/* End of cfg_event.h */

The EID_HTTPD_REBOOT is part of the httpdEventType enumeration, so the value could be 8 or 9 depending on the definition of RTCONFIG_BHCOST_OPT. I did not investigate this much and went straight to testing with 9.

The final string I came up with, is:

"{\"httpd\":{\"eid\":\"9\",\"mac_list\":[\"xx:xx:xx:xx:xx:xx\"],\"data\":\"{}\"}}"

Sadly all the code related to this cfgmnt is prebuilt, so there is only a definition for the send_cfgmnt_event(event_msg) function in asuswrt-merlin.ng/release/src/router/cfg_mnt/cfg_lib.h :

Code:
extern int send_cfgmnt_event(char *msg);

I found that these binaries are running in the system. More specifically cfg_server is listening on TCP, UDP and UNIX sockets. I tried to send that string to the UNIX socket /var/run/cfgmnt_ipc_socket and it worked.

It would be nice if any of you could try to replicate the same test, and check if it's working or not.

To be precise, you need to install socat (the version already installed does not allow connections to Unix sockets) and pipe that string to it:

Code:
opkg update
opkg install socat
echo "{\"httpd\":{\"eid\":\"9\",\"mac_list\":[\"xx:xx:xx:xx:xx:xx\"],\"data\":\"{}\"}}" | /opt/bin/socat - unix-connect:/var/run/cfgmnt_ipc_socket
 
Last edited:
I confirm this works flawlessly in my modest setup with AX58_V2 as main router and a good old AC66_B1 as a mesh node.
TL;DR
The full story.
I was looking for something similar in terms of functionality. I want to turn on/off the radio(s) of the mesh node on schedule but there is no such option in the GUI. At least not that I am aware about. It can be done by setting the appropriate NVRAM variables and restarting mesh node’s wireless, but the main router knows nothing about this and the radio buttons in the AiMesh management page are not in sync with the current state. So, I tried to mimic this behavior by CURL logging in and "clicking" the button. And it worked. Every time I open the AiMesh page, the radio buttons show the current state of the mesh node. But for this to work, the HTTPD process on the main router has to be restarted twice, before and after the "click". The first is needed to ensure that there is no active login, and the second is to enable immediate login. It's quite tedious actually.
Then I found this topic and looked for an opportunity to change the configuration which is what the main router does when it switches the radios of the mesh nodes. After a few tries, I came up with the following:
Code:
radio_24_off='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl0_radio":0}}}'
radio_24_on='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl0_radio":1}}}'
radio_50_off='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl1_radio":0}}}'
radio_50_on='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl1_radio":1}}}'
They could be combined:
Code:
radio_all_on='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl0_radio":1,"wl1_radio":1}}}'
radio_all_off='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl0_radio":0,"wl1_radio":0}}}'
Then send the message:
Code:
echo <message> | /opt/bin/socat - unix-connect:/var/run/cfgmnt_ipc_socket
Pay attention to where the double quotes are!
The differences from the restart message are:
  • eid = 6, which stands for EID_HTTPD_CONFIG_CHANGED
  • "re_mac" as string instead of "mac_list" array as target
The result is an instant on/off of the mesh node radios and the buttons in the AiMesh management page show the actual state. And no authentication is required.
Perfect!
The names for wireless radios may vary between models but for my setup (AX58_V2/AC66_B1) they are as described above.
I found another configuration change that can be made in the same way. To turn on/off mesh node LEDs the message should be:
Code:
led_off='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"led_val":0}}}'
led_on='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"led_val":1}}}'
CFG_SERVER tells nothing in answer even if message is wrong!
Now I am looking for a way to use UDP/TCP instead of UNIX socket. At least CFG_CLIENT on the mesh node receives messages over the network so this should be possible. Also, the built-in SOCAT will suffice. So far I have found that CFG_SERVER sends a broadcast UDP datagram as in:
Code:
... | socat - udp-datagram:AAA.BBB.CCC.DDD:7788, broadcast
but I haven't found a way to construct the message. Its payload has fixed size of 76 bytes and only a small part of it changes every time. Even if the message is the same.

Last but not the least!
Be aware that this can be a serious threat if not conducted properly and as intended. Certainly, there are practical use cases. But still, vulnerabilities have been found in CFG_SERVER/CFG_CLIENT in the past, which ASUS had to address. You have been warned.
 
I confirm this works flawlessly in my modest setup with AX58_V2 as main router and a good old AC66_B1 as a mesh node.
TL;DR
The full story.
I was looking for something similar in terms of functionality. I want to turn on/off the radio(s) of the mesh node on schedule but there is no such option in the GUI. At least not that I am aware about. It can be done by setting the appropriate NVRAM variables and restarting mesh node’s wireless, but the main router knows nothing about this and the radio buttons in the AiMesh management page are not in sync with the current state. So, I tried to mimic this behavior by CURL logging in and "clicking" the button. And it worked. Every time I open the AiMesh page, the radio buttons show the current state of the mesh node. But for this to work, the HTTPD process on the main router has to be restarted twice, before and after the "click". The first is needed to ensure that there is no active login, and the second is to enable immediate login. It's quite tedious actually.
Then I found this topic and looked for an opportunity to change the configuration which is what the main router does when it switches the radios of the mesh nodes. After a few tries, I came up with the following:
Code:
radio_24_off='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl0_radio":0}}}'
radio_24_on='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl0_radio":1}}}'
radio_50_off='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl1_radio":0}}}'
radio_50_on='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl1_radio":1}}}'
They could be combined:
Code:
radio_all_on='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl0_radio":1,"wl1_radio":1}}}'
radio_all_off='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"wl0_radio":0,"wl1_radio":0}}}'
Then send the message:
Code:
echo <message> | /opt/bin/socat - unix-connect:/var/run/cfgmnt_ipc_socket
Pay attention to where the double quotes are!
The differences from the restart message are:
  • eid = 6, which stands for EID_HTTPD_CONFIG_CHANGED
  • "re_mac" as string instead of "mac_list" array as target
The result is an instant on/off of the mesh node radios and the buttons in the AiMesh management page show the actual state. And no authentication is required.
Perfect!
The names for wireless radios may vary between models but for my setup (AX58_V2/AC66_B1) they are as described above.
I found another configuration change that can be made in the same way. To turn on/off mesh node LEDs the message should be:
Code:
led_off='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"led_val":0}}}'
led_on='{"httpd":{"eid":6,"re_mac":"xx:xx:xx:xx:xx:xx","config":{"led_val":1}}}'
CFG_SERVER tells nothing in answer even if message is wrong!
Now I am looking for a way to use UDP/TCP instead of UNIX socket. At least CFG_CLIENT on the mesh node receives messages over the network so this should be possible. Also, the built-in SOCAT will suffice. So far I have found that CFG_SERVER sends a broadcast UDP datagram as in:
Code:
... | socat - udp-datagram:AAA.BBB.CCC.DDD:7788, broadcast
but I haven't found a way to construct the message. Its payload has fixed size of 76 bytes and only a small part of it changes every time. Even if the message is the same.

Last but not the least!
Be aware that this can be a serious threat if not conducted properly and as intended. Certainly, there are practical use cases. But still, vulnerabilities have been found in CFG_SERVER/CFG_CLIENT in the past, which ASUS had to address. You have been warned.

Awesome. When I wrote this post I hoped for people like you to step in, so that most of the events could be mapped easily. This helped me for a good while, but recently I changed setup. It's very interesting nonetheless.

Regarding the UDP part, did you sniff the packet with wireshark?
 
Yes.
But honestly, I am quite skeptical about going in that direction. As you said, it's closed code and it will be difficult to decipher the message.
Message interception was more of a proof of concept, but I don't think it's worth the effort.
 

Latest threads

Support SNBForums w/ Amazon

If you'd like to support SNBForums, just use this link and buy anything on Amazon. Thanks!

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Back
Top