Superbox Hacks V1 - Login and Fetch SMSes

Not so Superbox

I have received a Turkcell Superbox from my ISP a while ago. It’s a rebranded ZTE MF286R. I am a tin foil hat wearer and it’s a nightmare fuel for me to have a black box residing in my network which runs proprietary software and frequently phones home. Most of wireless routers are like this and my ISP provided router is not an exception.

It’s not a good aspect of the device to find out that there’s no chance to reflash its' firmware with some free alternatives like OpenWrt or DD-WRT, yet. So I decided to fiddle with it to see if I can adjust a thing or two in order to mitigate the possible unwanted behaviors.

Then it came to my attention that this device can send and receive SMS. I think it might be useful for smart home applications so I would not have to use another platform to maintain for sending sensor information, power outage (yes, it has a battery input!), and whatnot.

Precautionary Measures

Please do not blame me if you blow up your precious device. Proceed only if you know what you are doing. I do not want to imply that I know what I am doing but, whatever…

I’m concerned if my intervention somehow gets evaluated as abuse of service, so it may (or may not) be a wise decision to minimize the vectors for remote control.

There are some extra menus and options hidden from user. Fortunately they are hidden using client side methods. I will talk about a few of them but you can find more by searching for isRootUser and display: none with Inspector tool.

There is a page where it’s possible to import and export device configuration. In case we mess up the settings, we may restore the device back to a working state.

1. Export Device Settings

Because I have found its' URL, you don’t have to. You can directly navigate to the related page from http://192.168.1.1/index.html#configuragtion [sic]. But if you are ascetic you can follow the next steps to find it yourself.

  1. Go to Advanced Settings > Others

  2. Open the Developer Tools of your browser, switch to the Inspector tab and search for isRootUser and remove the inline CSS style (style="display: none;") of second occurence.

    Remote Management - Before Edit - Preview

    • Inspector view for Backup & Restore element
  3. Now you should see the Backup & Restore link. Enter it.

    Remote Management - Before Edit - Preview

    • Visible Backup & Restore link
  4. Now press the yellow Export button and it will prompt you to save a file named CPE_settings.dat. Save it. I hope we will never need it…

2. Disable TR-069

TR-069 is a protocol that’s used for remote management of customer devices. It’s actually a great tool and not inherently evil. But I cannot trust my ISP so I disable it. By disabling it, I know that I will lose access to some cool features in customer portal of the ISP website. This may also cause some headache when you face a network problem and call your ISP because they use it to diagnose the problem. You’re on your own.

  1. TR-069 is at http://192.168.1.1/index.html#tr069config. But if you want to make it visible yourself, it’s listed on the same page with Backup & Restore. Follow the same steps in previous headline but remove the inline CSS style from first occurence instead of second. Then enter it.

    Remote Management - Before Edit - Preview

    • Preview of TR069Config page
  2. Disable Periodic Inform.

  3. Note down the default values just in case and then replace Server Username and Connection Request Username with something invalid.

  4. Apply changes.

3. Disable Remote Management

I have seen an option named Remote Management in Advanced Settings > System Security path. It’s invisible by default but it’s easy to make it visible. If Remote Management is enabled, an IP Address input becomes available and it’s default value is 85.29.0.0/18;85.29.13.0/27;. Apparently 85.29.0.0 range belongs to TELLCOM, which is a subsidiary of Turkcell Superonline. We better disable it. But how?

  1. Navigate to the System Security: http://192.168.1.1/index.html#system_security

    Remote Management - Before Edit - Preview

    • This is default preview of the page
  2. Repeat the instructions on the second step of Export Device Settings, search for isRootUser but this time remove the inline CSS style of first occurence.

    Remote Management - Before Edit - Inspector

    • Inspector view for Remote Management element
  3. If it is enabled, disable it and Apply changes.

    Remote Management - After Edit - Preview

    • Remote Management option became visible

The Script

I’ve automated the login-and-fetch process with a simple shell script. Here’s the summary of what this script does:

  1. Obtain required parameters
  2. Compose necessary hash for login request
  3. Send a POST request to login
  4. Verify login
  5. Use login cookie to fetch SMSes
  6. Print them

How Does It Work

I will try to explain the flow of the script but before that, I want to speak a little about my environment. I’m using Ubuntu 18.04 and the script utilizes cURL, jq and some other basic tools. I use jq to filter out JSON data from the response of cURL.

To install the dependencies: apt install jq curl perl

URL Templates

At the top of the main() function, 49th line, there are 2 URL string we will use frequently. URL_GET_CMD is used on GET requests and URL_SET_CMD is used on POST requests. isTest=false and request time in epoch format were also taking place in requests so I cargo-culted to minimize the amount of surprises.

URL_GET_CMD="http://$ROUTER_IP/goform/goform_get_cmd_process?isTest=false&_=$(epoch)"
URL_SET_CMD="http://$ROUTER_IP/goform/goform_set_cmd_process"

Retrieving Parameters

On line 14, there is get_cmd function.

get_cmd() {
    curl -s -H "$HEADER_REF" "$URL_GET_CMD&cmd=$1" \
        | jq -r ".$1"
}

I’ve basically tried to imitate the following GET request with bare minimum parameters.

Request Headers

In turn, you receive a JSON response like below.

Request Response

I could not find an elegant way to request multiple parameters then parse them at a time so I’ve limited my get_cmd to a single argument. After that pipe cURL output to jq with -r switch to get the raw data without double quotes and store it in a variable.

Sending a Login Request

We have a sample login request here.

Request Response

There were two different unknown values. First one was easy to demystify: password. The value you enter in the password field of login form was Base64 encoded. That’s all. The second one, AD was slightly harder. It was obviously looking like an MD5 but what input is passed to the MD5 function?

Before the login request, client (your PC) sends a GET request asking for a value named RD. Then uses two different values, namely rd0 and rd1. I have tried to understand their meaning and after a debug period, I have seen that their names mean wa_inner_version and cr_version respectively in line 316 of (pretty printed) service.js. I guess cr_version is customer version and wa_inner_version is probably internal naming of the firmware.

rd0 and rd1

Finally, this is how AD is forged. JS source was partially obfuscated so it was a bit of an hassle to locate the clues. It works like that: md5(md5(rd0 + rd1) + RD).

AD Formula

From line 54 to line 68 in the script, we calculate AD.

# get RD
RD=$(get_cmd "RD")
# get rd0 a.k.a. rd_params0 a.k.a. wa_inner_version
rd0=$(get_cmd "wa_inner_version")
# get rd1 a.k.a. rd_params1 a.k.a. cr_version
rd1=$(get_cmd "cr_version")
# compose AD with following formula: AD = md5(md5(rd0+rd1)+RD)

MD5_rd=$(echo -n "$rd0$rd1" \
    | md5sum \
    | awk '{print $1}')

AD=$(echo -n "$MD5_rd$RD" \
    | md5sum \
    | awk '{print $1}')

The actual login request takes place on line 76

LOGIN_RESULT=$(curl -s -c "$PATH_COOKIE_FILE" -H "$HEADER_REF" -H "$HEADER_CONTENT_TYPE" -d "$LOGIN_PARAMS" "$URL_SET_CMD" | jq -r ".result")

Append user ID, Base64 encoded password and AD to the $LOGIN_PARAMS, set Content-Type as application/x-www-form-urlencoded and send a POST request. If response is 0 it means success. We also receive a cookie named zwsd on a sucessful login response, which will be used on further requests.

Fetching Messages

We have successfully logged in to the UI. So far so good. Now it’s time to do the actual work. I chose the task of reading the list of SMS from the device.

Client side sends a GET request with some basic pagination parameters and includes zwsd cookie, obtained on the previous paragraph, in response header. This is how I accomplish this, as seen on line 112.

QUERY_SMS="sms_data_total&page=0&data_per_page=500&mem_store=1&tags=10&order_by=order+by+id+desc"
MSG_RESPONSE="$(curl -s -H "$HEADER_REF" -H "$COOKIE_PARAM" "$URL_GET_CMD&cmd=$QUERY_SMS")"

GET Request for SMS

There we see the response. It includes phone number, send/receive time in a custom format, and message content as hex encoded UTF-8, and some other different values.

SMS array response

There’s a sketchy demonstration of the print process.

# Some contacts include "space" in their name. If we do not ignore "space",
# it would be split apart into multiple lines.
IFS=$'\n'
for msg in $(echo "$MSG_RESPONSE" | jq -c '.messages | .[]'); do
    # echo "$msg"
    MSG_ID="$(echo "$msg" | jq -r '.id')"
    MSG_NUMBER="$(echo "$msg" | jq -r '.number')"
    MSG_DATE_RAW="$(echo "$msg" | jq -r '.date')"
    MSG_TEXT_RAW="$(echo "$msg" | jq -r '.content')"
    MSG_DATE=$(parse_date "$MSG_DATE_RAW")
    MSG_TEXT=$(hex_to_utf8 "$MSG_TEXT_RAW")
    printf "[%3d] %s | %s\n" "$MSG_ID" "$MSG_NUMBER" "$MSG_DATE"
    echo "- - - - - - - - - - - - - - - - - - - - - - -"
    echo "$MSG_TEXT"
    # echo "$MSG_TEXT_RAW"
    # echo "$MSG_TEXT_RAW" | xxd -ps -r
    printf "\n"
done

… and its' output:

Script output

That’s All

It was a fun journey for me. I am planning to create a script which makes it possible to send an SMS for the next chapter.

Please send an email to gedizaydindogmus[at]gmail.com if you see something wrong, notice a typo, or just to say hello!